From d30596195d9335aa07609d74bbf43b1170c30fb3 Mon Sep 17 00:00:00 2001 From: Andrew Morton Date: Fri, 12 Mar 2021 16:15:04 -0800 Subject: [PATCH] Add modules --- composer.json | 2 + composer.lock | 680 +++++++++- .../block.block.democraticli_page_title.yml | 15 +- config/sync/config_ignore.settings.yml | 4 - config/sync/core.extension.yml | 3 +- config/sync/simple_page_manager.page.home.yml | 42 + config/sync/system.site.yml | 4 +- config/sync/system.theme.global.yml | 2 +- vendor/composer/InstalledVersions.php | 76 +- vendor/composer/autoload_files.php | 2 +- vendor/composer/autoload_psr4.php | 7 +- vendor/composer/autoload_static.php | 30 +- vendor/composer/installed.json | 702 ++++++++++ vendor/composer/installed.php | 76 +- vendor/doctrine/cache/LICENSE | 19 + vendor/doctrine/cache/README.md | 9 + vendor/doctrine/cache/UPGRADE.md | 16 + vendor/doctrine/cache/composer.json | 52 + .../lib/Doctrine/Common/Cache/ApcCache.php | 105 ++ .../lib/Doctrine/Common/Cache/ApcuCache.php | 106 ++ .../lib/Doctrine/Common/Cache/ArrayCache.php | 113 ++ .../cache/lib/Doctrine/Common/Cache/Cache.php | 90 ++ .../Doctrine/Common/Cache/CacheProvider.php | 325 +++++ .../lib/Doctrine/Common/Cache/ChainCache.php | 195 +++ .../Doctrine/Common/Cache/ClearableCache.php | 21 + .../Common/Cache/CouchbaseBucketCache.php | 197 +++ .../Doctrine/Common/Cache/CouchbaseCache.php | 105 ++ .../Doctrine/Common/Cache/ExtMongoDBCache.php | 198 +++ .../lib/Doctrine/Common/Cache/FileCache.php | 281 ++++ .../Doctrine/Common/Cache/FilesystemCache.php | 102 ++ .../Doctrine/Common/Cache/FlushableCache.php | 18 + .../Doctrine/Common/Cache/InvalidCacheId.php | 25 + .../Common/Cache/LegacyMongoDBCache.php | 175 +++ .../Doctrine/Common/Cache/MemcacheCache.php | 104 ++ .../Doctrine/Common/Cache/MemcachedCache.php | 170 +++ .../Doctrine/Common/Cache/MongoDBCache.php | 112 ++ .../Common/Cache/MultiDeleteCache.php | 22 + .../Doctrine/Common/Cache/MultiGetCache.php | 23 + .../Common/Cache/MultiOperationCache.php | 12 + .../Doctrine/Common/Cache/MultiPutCache.php | 24 + .../Doctrine/Common/Cache/PhpFileCache.php | 118 ++ .../lib/Doctrine/Common/Cache/PredisCache.php | 143 ++ .../lib/Doctrine/Common/Cache/RedisCache.php | 181 +++ .../Doctrine/Common/Cache/SQLite3Cache.php | 206 +++ .../lib/Doctrine/Common/Cache/Version.php | 8 + .../lib/Doctrine/Common/Cache/VoidCache.php | 59 + .../Doctrine/Common/Cache/WinCacheCache.php | 106 ++ .../lib/Doctrine/Common/Cache/XcacheCache.php | 104 ++ .../Doctrine/Common/Cache/ZendDataCache.php | 69 + .../collections/.doctrine-project.json | 17 + vendor/doctrine/collections/CONTRIBUTING.md | 54 + vendor/doctrine/collections/LICENSE | 19 + vendor/doctrine/collections/README.md | 92 ++ vendor/doctrine/collections/composer.json | 37 + .../docs/en/derived-collections.rst | 26 + .../docs/en/expression-builder.rst | 173 +++ .../collections/docs/en/expressions.rst | 102 ++ vendor/doctrine/collections/docs/en/index.rst | 327 +++++ .../collections/docs/en/lazy-collections.rst | 26 + .../doctrine/collections/docs/en/sidebar.rst | 8 + .../Collections/AbstractLazyCollection.php | 369 ++++++ .../Common/Collections/ArrayCollection.php | 441 +++++++ .../Common/Collections/Collection.php | 297 +++++ .../Doctrine/Common/Collections/Criteria.php | 222 ++++ .../Expr/ClosureExpressionVisitor.php | 262 ++++ .../Common/Collections/Expr/Comparison.php | 80 ++ .../Collections/Expr/CompositeExpression.php | 68 + .../Common/Collections/Expr/Expression.php | 14 + .../Collections/Expr/ExpressionVisitor.php | 55 + .../Common/Collections/Expr/Value.php | 33 + .../Common/Collections/ExpressionBuilder.php | 180 +++ .../Common/Collections/Selectable.php | 32 + vendor/doctrine/collections/psalm.xml.dist | 54 + vendor/doctrine/common/.doctrine-project.json | 18 + vendor/doctrine/common/.github/FUNDING.yml | 3 + vendor/doctrine/common/LICENSE | 19 + vendor/doctrine/common/README.md | 11 + vendor/doctrine/common/UPGRADE_TO_2_1 | 39 + vendor/doctrine/common/UPGRADE_TO_2_2 | 61 + vendor/doctrine/common/composer.json | 54 + vendor/doctrine/common/docs/en/index.rst | 10 + .../docs/en/reference/class-loading.rst | 242 ++++ vendor/doctrine/common/humbug.json.dist | 11 + .../lib/Doctrine/Common/ClassLoader.php | 267 ++++ .../lib/Doctrine/Common/CommonException.php | 13 + .../common/lib/Doctrine/Common/Comparable.php | 28 + .../common/lib/Doctrine/Common/Lexer.php | 25 + .../Common/Proxy/AbstractProxyFactory.php | 246 ++++ .../lib/Doctrine/Common/Proxy/Autoloader.php | 80 ++ .../Exception/InvalidArgumentException.php | 106 ++ .../Proxy/Exception/OutOfBoundsException.php | 24 + .../Common/Proxy/Exception/ProxyException.php | 13 + .../Exception/UnexpectedValueException.php | 70 + .../lib/Doctrine/Common/Proxy/Proxy.php | 72 ++ .../Doctrine/Common/Proxy/ProxyDefinition.php | 51 + .../Doctrine/Common/Proxy/ProxyGenerator.php | 1152 +++++++++++++++++ .../lib/Doctrine/Common/Util/ClassUtils.php | 91 ++ .../common/lib/Doctrine/Common/Util/Debug.php | 167 +++ .../lib/Doctrine/Common/Util/Inflector.php | 19 + .../common/lib/Doctrine/Common/Version.php | 37 + vendor/doctrine/common/phpstan.neon.dist | 46 + .../event-manager/.doctrine-project.json | 18 + vendor/doctrine/event-manager/LICENSE | 19 + vendor/doctrine/event-manager/README.md | 13 + vendor/doctrine/event-manager/composer.json | 50 + .../lib/Doctrine/Common/EventArgs.php | 45 + .../lib/Doctrine/Common/EventManager.php | 132 ++ .../lib/Doctrine/Common/EventSubscriber.php | 21 + vendor/doctrine/inflector/LICENSE | 19 + vendor/doctrine/inflector/README.md | 8 + vendor/doctrine/inflector/composer.json | 42 + vendor/doctrine/inflector/docs/en/index.rst | 226 ++++ .../Doctrine/Common/Inflector/Inflector.php | 281 ++++ .../Inflector/CachedWordInflector.php | 24 + .../GenericLanguageInflectorFactory.php | 65 + .../lib/Doctrine/Inflector/Inflector.php | 506 ++++++++ .../Doctrine/Inflector/InflectorFactory.php | 45 + .../lib/Doctrine/Inflector/Language.php | 19 + .../Inflector/LanguageInflectorFactory.php | 33 + .../Doctrine/Inflector/NoopWordInflector.php | 13 + .../Inflector/Rules/English/Inflectible.php | 182 +++ .../Rules/English/InflectorFactory.php | 21 + .../Inflector/Rules/English/Rules.php | 31 + .../Inflector/Rules/English/Uninflected.php | 193 +++ .../Inflector/Rules/French/Inflectible.php | 49 + .../Rules/French/InflectorFactory.php | 21 + .../Doctrine/Inflector/Rules/French/Rules.php | 31 + .../Inflector/Rules/French/Uninflected.php | 34 + .../Rules/NorwegianBokmal/Inflectible.php | 40 + .../NorwegianBokmal/InflectorFactory.php | 21 + .../Inflector/Rules/NorwegianBokmal/Rules.php | 31 + .../Rules/NorwegianBokmal/Uninflected.php | 36 + .../lib/Doctrine/Inflector/Rules/Pattern.php | 42 + .../lib/Doctrine/Inflector/Rules/Patterns.php | 34 + .../Rules/Portuguese/Inflectible.php | 104 ++ .../Rules/Portuguese/InflectorFactory.php | 21 + .../Inflector/Rules/Portuguese/Rules.php | 31 + .../Rules/Portuguese/Uninflected.php | 38 + .../lib/Doctrine/Inflector/Rules/Ruleset.php | 39 + .../Inflector/Rules/Spanish/Inflectible.php | 53 + .../Rules/Spanish/InflectorFactory.php | 21 + .../Inflector/Rules/Spanish/Rules.php | 31 + .../Inflector/Rules/Spanish/Uninflected.php | 36 + .../Doctrine/Inflector/Rules/Substitution.php | 30 + .../Inflector/Rules/Substitutions.php | 56 + .../Inflector/Rules/Transformation.php | 38 + .../Inflector/Rules/Transformations.php | 29 + .../Inflector/Rules/Turkish/Inflectible.php | 40 + .../Rules/Turkish/InflectorFactory.php | 21 + .../Inflector/Rules/Turkish/Rules.php | 31 + .../Inflector/Rules/Turkish/Uninflected.php | 36 + .../lib/Doctrine/Inflector/Rules/Word.php | 21 + .../Doctrine/Inflector/RulesetInflector.php | 55 + .../lib/Doctrine/Inflector/WordInflector.php | 10 + vendor/doctrine/inflector/phpstan.neon.dist | 13 + .../persistence/.doctrine-project.json | 24 + .../workflows/continuous-integration.yml | 38 + vendor/doctrine/persistence/LICENSE | 19 + vendor/doctrine/persistence/README.md | 12 + vendor/doctrine/persistence/UPGRADE-1.2.md | 6 + vendor/doctrine/persistence/composer.json | 56 + .../Doctrine/Common/NotifyPropertyChanged.php | 16 + .../Persistence/AbstractManagerRegistry.php | 16 + .../Common/Persistence/ConnectionRegistry.php | 16 + .../Persistence/Event/LifecycleEventArgs.php | 16 + .../Event/LoadClassMetadataEventArgs.php | 16 + .../Persistence/Event/ManagerEventArgs.php | 16 + .../Persistence/Event/OnClearEventArgs.php | 16 + .../Persistence/Event/PreUpdateEventArgs.php | 16 + .../Common/Persistence/ManagerRegistry.php | 16 + .../Mapping/AbstractClassMetadataFactory.php | 16 + .../Persistence/Mapping/ClassMetadata.php | 16 + .../Mapping/ClassMetadataFactory.php | 16 + .../Mapping/Driver/AnnotationDriver.php | 16 + .../Mapping/Driver/DefaultFileLocator.php | 16 + .../Persistence/Mapping/Driver/FileDriver.php | 16 + .../Mapping/Driver/FileLocator.php | 16 + .../Mapping/Driver/MappingDriver.php | 16 + .../Mapping/Driver/MappingDriverChain.php | 16 + .../Persistence/Mapping/Driver/PHPDriver.php | 16 + .../Mapping/Driver/StaticPHPDriver.php | 16 + .../Mapping/Driver/SymfonyFileLocator.php | 16 + .../Persistence/Mapping/MappingException.php | 16 + .../Persistence/Mapping/ReflectionService.php | 16 + .../Mapping/RuntimeReflectionService.php | 16 + .../Mapping/StaticReflectionService.php | 16 + .../Common/Persistence/ObjectManager.php | 16 + .../Common/Persistence/ObjectManagerAware.php | 16 + .../Persistence/ObjectManagerDecorator.php | 16 + .../Common/Persistence/ObjectRepository.php | 16 + .../Common/Persistence/PersistentObject.php | 235 ++++ .../lib/Doctrine/Common/Persistence/Proxy.php | 16 + .../Common/PropertyChangedListener.php | 16 + .../Persistence/AbstractManagerRegistry.php | 251 ++++ .../Persistence/ConnectionRegistry.php | 43 + .../Persistence/Event/LifecycleEventArgs.php | 63 + .../Event/LoadClassMetadataEventArgs.php | 48 + .../Persistence/Event/ManagerEventArgs.php | 33 + .../Persistence/Event/OnClearEventArgs.php | 61 + .../Persistence/Event/PreUpdateEventArgs.php | 116 ++ .../Doctrine/Persistence/ManagerRegistry.php | 92 ++ .../Mapping/AbstractClassMetadataFactory.php | 420 ++++++ .../Persistence/Mapping/ClassMetadata.php | 157 +++ .../Mapping/ClassMetadataFactory.php | 57 + .../Mapping/Driver/AnnotationDriver.php | 258 ++++ .../Mapping/Driver/DefaultFileLocator.php | 164 +++ .../Persistence/Mapping/Driver/FileDriver.php | 199 +++ .../Mapping/Driver/FileLocator.php | 57 + .../Mapping/Driver/MappingDriver.php | 41 + .../Mapping/Driver/MappingDriverChain.php | 149 +++ .../Persistence/Mapping/Driver/PHPDriver.php | 49 + .../Mapping/Driver/StaticPHPDriver.php | 134 ++ .../Mapping/Driver/SymfonyFileLocator.php | 233 ++++ .../Persistence/Mapping/MappingException.php | 98 ++ .../Persistence/Mapping/ReflectionService.php | 74 ++ .../Mapping/RuntimeReflectionService.php | 103 ++ .../Mapping/StaticReflectionService.php | 74 ++ .../Persistence/NotifyPropertyChanged.php | 25 + .../Doctrine/Persistence/ObjectManager.php | 160 +++ .../Persistence/ObjectManagerAware.php | 34 + .../Persistence/ObjectManagerDecorator.php | 120 ++ .../Doctrine/Persistence/ObjectRepository.php | 64 + .../Persistence/PropertyChangedListener.php | 26 + .../lib/Doctrine/Persistence/Proxy.php | 39 + vendor/doctrine/persistence/psalm.xml | 16 + .../contrib/ckeditor_div_manager/LICENSE.txt | 339 +++++ .../contrib/ckeditor_div_manager/README.md | 110 ++ .../ckeditor_div_manager.info.yml | 13 + .../ckeditor_div_manager.install | 41 + .../ckeditor_div_manager/composer.json | 18 + .../src/Plugin/CKEditorPlugin/CKEditorDiv.php | 44 + web/modules/contrib/devel/.docker/zz-php.ini | 5 + web/modules/contrib/devel/.gitignore | 33 + web/modules/contrib/devel/.gitlab-ci.yml | 29 + web/modules/contrib/devel/.travis.yml | 109 ++ web/modules/contrib/devel/CODEOWNERS | 3 + web/modules/contrib/devel/LICENSE.txt | 339 +++++ web/modules/contrib/devel/README.md | 55 + web/modules/contrib/devel/composer.json | 33 + .../devel/config/install/devel.settings.yml | 10 + .../config/install/devel.toolbar.settings.yml | 8 + .../config/install/system.menu.devel.yml | 10 + .../devel/config/schema/devel.schema.yml | 59 + web/modules/contrib/devel/css/devel.css | 28 + .../contrib/devel/css/devel.toolbar.css | 42 + web/modules/contrib/devel/devel.api.php | 25 + web/modules/contrib/devel/devel.info.yml | 14 + web/modules/contrib/devel/devel.install | 49 + web/modules/contrib/devel/devel.libraries.yml | 17 + .../contrib/devel/devel.links.menu.yml | 89 ++ .../contrib/devel/devel.links.task.yml | 18 + web/modules/contrib/devel/devel.module | 758 +++++++++++ .../contrib/devel/devel.permissions.yml | 9 + web/modules/contrib/devel/devel.routing.yml | 288 +++++ web/modules/contrib/devel/devel.services.yml | 32 + .../contrib/devel/devel_generate/README.md | 57 + .../devel/devel_generate/composer.json | 52 + .../devel_generate/devel_generate.batch.inc | 34 + .../devel_generate/devel_generate.info.yml | 13 + .../devel_generate/devel_generate.module | 177 +++ .../devel_generate.permissions.yml | 5 + .../devel_generate/devel_generate.routing.yml | 2 + .../devel_generate.services.yml | 4 + .../devel/devel_generate/drush.services.yml | 6 + .../src/Annotation/DevelGenerate.php | 85 ++ .../src/Commands/DevelGenerateCommands.php | 285 ++++ .../devel_generate/src/DevelGenerateBase.php | 307 +++++ .../src/DevelGenerateBaseInterface.php | 83 ++ .../src/DevelGenerateException.php | 10 + .../src/DevelGeneratePermissions.php | 61 + .../src/DevelGeneratePluginManager.php | 53 + .../src/Form/DevelGenerateForm.php | 113 ++ .../DevelGenerate/ContentDevelGenerate.php | 728 +++++++++++ .../DevelGenerate/MediaDevelGenerate.php | 547 ++++++++ .../DevelGenerate/MenuDevelGenerate.php | 426 ++++++ .../DevelGenerate/TermDevelGenerate.php | 477 +++++++ .../DevelGenerate/UserDevelGenerate.php | 206 +++ .../DevelGenerate/VocabularyDevelGenerate.php | 174 +++ .../src/Routing/DevelGenerateRoutes.php | 73 ++ .../devel_generate_example.info.yml | 13 + .../DevelGenerate/ExampleDevelGenerate.php | 97 ++ .../Functional/DevelGenerateBrowserTest.php | 412 ++++++ .../DevelGenerateBrowserTestBase.php | 52 + .../Functional/DevelGenerateCommandsTest.php | 229 ++++ .../src/Traits/DevelGenerateSetupTrait.php | 109 ++ .../src/Unit/DevelGenerateManagerTest.php | 86 ++ web/modules/contrib/devel/drush.services.yml | 6 + .../contrib/devel/icons/bebebe/cog.svg | 1 + .../contrib/devel/icons/ffffff/cog.svg | 1 + web/modules/contrib/devel/icons/folder.png | Bin 0 -> 138695 bytes web/modules/contrib/devel/phpcs.xml.dist | 41 + .../devel/src/Annotation/DevelDumper.php | 37 + .../devel/src/Commands/DevelCommands.php | 320 +++++ .../Controller/ContainerInfoController.php | 257 ++++ .../devel/src/Controller/DevelController.php | 226 ++++ .../src/Controller/ElementInfoController.php | 145 +++ .../src/Controller/EntityDebugController.php | 142 ++ .../Controller/EntityTypeInfoController.php | 180 +++ .../src/Controller/EventInfoController.php | 121 ++ .../src/Controller/LayoutInfoController.php | 82 ++ .../src/Controller/RouteInfoController.php | 195 +++ .../src/Controller/SwitchUserController.php | 120 ++ .../contrib/devel/src/DevelDumperBase.php | 87 ++ .../devel/src/DevelDumperInterface.php | 59 + .../contrib/devel/src/DevelDumperManager.php | 148 +++ .../devel/src/DevelDumperManagerInterface.php | 104 ++ .../devel/src/DevelDumperPluginManager.php | 71 + .../src/DevelDumperPluginManagerInterface.php | 24 + .../src/Element/ClientSideFilterTable.php | 108 ++ .../contrib/devel/src/EntityTypeInfo.php | 103 ++ .../ErrorHandlerSubscriber.php | 57 + .../ThemeInfoRebuildSubscriber.php | 119 ++ .../contrib/devel/src/Form/ConfigEditor.php | 155 +++ .../contrib/devel/src/Form/ConfigsList.php | 86 ++ .../contrib/devel/src/Form/DevelReinstall.php | 166 +++ .../src/Form/RouterRebuildConfirmForm.php | 86 ++ .../contrib/devel/src/Form/SettingsForm.php | 225 ++++ .../contrib/devel/src/Form/SwitchUserForm.php | 110 ++ .../devel/src/Form/SystemStateEdit.php | 190 +++ .../devel/src/Form/ToolbarSettingsForm.php | 118 ++ .../src/Plugin/Block/SwitchUserBlock.php | 298 +++++ .../src/Plugin/Derivative/DevelLocalTask.php | 106 ++ .../src/Plugin/Devel/Dumper/DoctrineDebug.php | 71 + .../devel/src/Plugin/Devel/Dumper/Kint.php | 100 ++ .../src/Plugin/Devel/Dumper/VarDumper.php | 46 + .../devel/src/Plugin/Mail/DevelMailLog.php | 203 +++ .../src/Plugin/Menu/DestinationMenuLink.php | 32 + .../src/Plugin/Menu/RouteDetailMenuLink.php | 29 + .../devel/src/Render/FilteredMarkup.php | 23 + .../devel/src/Routing/RouteSubscriber.php | 156 +++ .../contrib/devel/src/ToolbarHandler.php | 189 +++ .../devel/src/Twig/Extension/Debug.php | 243 ++++ .../css/devel_dumper_test.css | 3 + .../devel_dumper_test.info.yml | 10 + .../devel_dumper_test.libraries.yml | 7 + .../devel_dumper_test.routing.yml | 40 + .../devel_dumper_test/js/devel_dumper_test.js | 0 .../src/Controller/DumperTestController.php | 106 ++ .../Devel/Dumper/AvailableTestDumper.php | 61 + .../Devel/Dumper/NotAvailableTestDumper.php | 41 + .../devel_entity_test.info.yml | 14 + .../devel_entity_test.links.task.yml | 2 + .../devel_entity_test.module | 29 + .../src/Entity/DevelEntityTestCanonical.php | 44 + .../src/Entity/DevelEntityTestEdit.php | 44 + .../src/Entity/DevelEntityTestNoLinks.php | 41 + .../Derivative/DevelEntityTestLocalTasks.php | 31 + .../modules/devel_test/devel_test.info.yml | 10 + .../modules/devel_test/devel_test.module | 20 + .../modules/devel_test/devel_test.routing.yml | 7 + .../devel_test/devel_test.services.yml | 7 + .../src/Controller/DevelTestController.php | 24 + .../src/Routing/TestRouteSubscriber.php | 38 + .../src/Functional/DevelBrowserTestBase.php | 55 + .../src/Functional/DevelCommandsTest.php | 44 + .../src/Functional/DevelContainerInfoTest.php | 245 ++++ .../src/Functional/DevelControllerTest.php | 145 +++ .../tests/src/Functional/DevelDumperTest.php | 145 +++ .../src/Functional/DevelElementInfoTest.php | 135 ++ .../Functional/DevelEntityTypeInfoTest.php | 170 +++ .../src/Functional/DevelErrorHandlerTest.php | 139 ++ .../src/Functional/DevelEventInfoTest.php | 119 ++ .../src/Functional/DevelLayoutInfoTest.php | 140 ++ .../src/Functional/DevelMenuLinksTest.php | 93 ++ .../Functional/DevelModulesReinstallTest.php | 50 + .../src/Functional/DevelRequirementsTest.php | 25 + .../src/Functional/DevelRouteInfoTest.php | 181 +++ .../src/Functional/DevelRouterRebuildTest.php | 36 + .../src/Functional/DevelStateEditorTest.php | 184 +++ .../src/Functional/DevelSwitchUserTest.php | 305 +++++ .../tests/src/Functional/DevelToolbarTest.php | 268 ++++ .../src/Functional/DevelWebAssertHelper.php | 34 + .../tests/src/Kernel/DevelDumperTestTrait.php | 133 ++ .../Kernel/DevelEnforcedDependenciesTest.php | 80 ++ .../tests/src/Kernel/DevelMailLogTest.php | 201 +++ .../tests/src/Kernel/DevelQueryDebugTest.php | 136 ++ .../src/Kernel/DevelTwigExtensionTest.php | 204 +++ .../Unit/DevelClientSideFilterTableTest.php | 204 +++ .../contrib/devel/webprofiler/README.md | 86 ++ .../contrib/devel/webprofiler/composer.json | 15 + .../config/install/webprofiler.config.yml | 28 + .../config/schema/webprofiler.schema.yml | 35 + .../devel/webprofiler/console.services.yml | 14 + .../translations/en/webprofiler.benchmark.yml | 20 + .../translations/en/webprofiler.export.yml | 15 + .../translations/en/webprofiler.list.yml | 14 + .../translations/es/webprofiler.benchmark.yml | 20 + .../translations/es/webprofiler.export.yml | 15 + .../translations/es/webprofiler.list.yml | 14 + .../devel/webprofiler/css/app/dashboard.css | 852 ++++++++++++ .../devel/webprofiler/css/timeline.css | 106 ++ .../devel/webprofiler/images/caret-down.svg | 1 + .../js/app/collections/collectors.js | 35 + .../devel/webprofiler/js/app/helpers.js | 107 ++ .../contrib/devel/webprofiler/js/app/main.js | 105 ++ .../webprofiler/js/app/models/collector.js | 15 + .../webprofiler/js/app/routers/collectors.js | 48 + .../webprofiler/js/app/views/collector.js | 29 + .../js/app/views/collectorsList.js | 21 + .../devel/webprofiler/js/app/views/details.js | 20 + .../devel/webprofiler/js/app/views/layout.js | 83 ++ .../contrib/devel/webprofiler/js/database.js | 68 + .../contrib/devel/webprofiler/js/timeline.js | 223 ++++ .../src/Access/AccessManagerWrapper.php | 84 ++ .../Asset/CssCollectionRendererWrapper.php | 41 + .../src/Asset/JsCollectionRendererWrapper.php | 41 + .../src/Cache/CacheBackendWrapper.php | 176 +++ .../src/Cache/CacheFactoryWrapper.php | 62 + .../src/Command/BenchmarkCommand.php | 312 +++++ .../webprofiler/src/Command/BenchmarkData.php | 57 + .../webprofiler/src/Command/ExportCommand.php | 164 +++ .../webprofiler/src/Command/ListCommand.php | 83 ++ .../src/Compiler/DecoratorPass.php | 34 + .../webprofiler/src/Compiler/ProfilerPass.php | 65 + .../webprofiler/src/Compiler/ServicePass.php | 109 ++ .../webprofiler/src/Compiler/StoragePass.php | 33 + .../src/Config/ConfigFactoryWrapper.php | 45 + .../src/Controller/DashboardController.php | 244 ++++ .../src/Controller/DatabaseController.php | 95 ++ .../src/Controller/ToolbarController.php | 111 ++ .../src/DataCollector/AssetsDataCollector.php | 108 ++ .../src/DataCollector/BlocksDataCollector.php | 147 +++ .../src/DataCollector/CacheDataCollector.php | 173 +++ .../src/DataCollector/ConfigDataCollector.php | 69 + .../DataCollector/DatabaseDataCollector.php | 266 ++++ .../src/DataCollector/DevelDataCollector.php | 103 ++ .../src/DataCollector/DrupalDataCollector.php | 139 ++ .../DrupalDataCollectorTrait.php | 122 ++ .../src/DataCollector/EventsDataCollector.php | 145 +++ .../DataCollector/ExtensionDataCollector.php | 157 +++ .../src/DataCollector/FormsDataCollector.php | 87 ++ .../src/DataCollector/HttpDataCollector.php | 176 +++ .../src/DataCollector/MailDataCollector.php | 103 ++ .../PerformanceTimingDataCollector.php | 84 ++ .../DataCollector/PhpConfigDataCollector.php | 163 +++ .../DataCollector/RequestDataCollector.php | 112 ++ .../DataCollector/RoutingDataCollector.php | 92 ++ .../DataCollector/ServicesDataCollector.php | 163 +++ .../src/DataCollector/StateDataCollector.php | 66 + .../src/DataCollector/ThemeDataCollector.php | 258 ++++ .../src/DataCollector/TimeDataCollector.php | 157 +++ .../TranslationsDataCollector.php | 100 ++ .../src/DataCollector/UserDataCollector.php | 144 +++ .../src/DataCollector/ViewsDataCollector.php | 118 ++ .../devel/webprofiler/src/Decorator.php | 95 ++ .../TraceableContainer.php | 67 + .../src/DrupalDataCollectorInterface.php | 68 + .../Config/ConfigEntityStorageDecorator.php | 229 ++++ .../Config/DomainStorageDecorator.php | 90 ++ .../Config/ImageStyleStorageDecorator.php | 33 + .../Config/RoleStorageDecorator.php | 19 + .../Config/ShortcutSetStorageDecorator.php | 56 + .../Config/VocabularyStorageDecorator.php | 19 + .../src/Entity/EntityDecorator.php | 24 + .../src/Entity/EntityManagerWrapper.php | 263 ++++ .../src/Entity/EntityViewBuilderDecorator.php | 93 ++ .../EventDispatcherTraceableInterface.php | 22 + .../TraceableEventDispatcher.php | 227 ++++ .../EventSubscriber/ProfilerSubscriber.php | 133 ++ .../WebprofilerEventSubscriber.php | 98 ++ .../devel/webprofiler/src/Form/ConfigForm.php | 239 ++++ .../src/Form/FormBuilderWrapper.php | 62 + .../src/Form/ProfilesFilterForm.php | 87 ++ .../src/Frontend/PerformanceTimingData.php | 82 ++ .../webprofiler/src/Helper/ClassShortener.php | 35 + .../src/Helper/ClassShortenerInterface.php | 17 + .../src/Helper/IdeLinkGenerator.php | 41 + .../src/Helper/IdeLinkGeneratorInterface.php | 18 + .../src/Http/HttpClientMiddleware.php | 95 ++ .../src/Mail/MailManagerWrapper.php | 106 ++ .../src/Profiler/DatabaseProfilerStorage.php | 160 +++ .../src/Profiler/FileProfilerStorage.php | 28 + .../webprofiler/src/Profiler/Profiler.php | 78 ++ .../src/Profiler/ProfilerStorageFactory.php | 26 + .../src/Profiler/ProfilerStorageManager.php | 52 + .../src/Profiler/TemplateManager.php | 131 ++ .../src/ProxyClass/Command/ListCommand.php | 325 +++++ .../WebprofilerRequestMatcher.php | 51 + .../src/Routing/TokenConverter.php | 47 + .../StackMiddleware/WebprofilerMiddleware.php | 41 + .../webprofiler/src/State/StateWrapper.php | 125 ++ .../devel/webprofiler/src/Stopwatch.php | 609 +++++++++ .../TranslationManagerWrapper.php | 71 + .../src/Theme/ThemeNegotiatorWrapper.php | 82 ++ .../src/Twig/Dumper/HtmlDumper.php | 45 + .../src/Twig/Extension/ProfilerExtension.php | 94 ++ .../src/Views/TraceableViewExecutable.php | 157 +++ .../Views/ViewExecutableFactoryWrapper.php | 48 + .../src/WebprofilerServiceProvider.php | 116 ++ .../templates/Collector/assets.html.twig | 133 ++ .../templates/Collector/blocks.html.twig | 113 ++ .../templates/Collector/cache.html.twig | 63 + .../templates/Collector/config.html.twig | 37 + .../templates/Collector/database.html.twig | 168 +++ .../templates/Collector/devel.html.twig | 24 + .../templates/Collector/drupal.html.twig | 66 + .../templates/Collector/events.html.twig | 88 ++ .../templates/Collector/extensions.html.twig | 95 ++ .../templates/Collector/forms.html.twig | 67 + .../templates/Collector/http.html.twig | 77 ++ .../templates/Collector/mail.html.twig | 76 ++ .../Collector/performance_timing.html.twig | 76 ++ .../templates/Collector/php_config.html.twig | 52 + .../templates/Collector/request.html.twig | 216 ++++ .../templates/Collector/routing.html.twig | 50 + .../templates/Collector/services.html.twig | 149 +++ .../templates/Collector/state.html.twig | 36 + .../templates/Collector/theme.html.twig | 147 +++ .../templates/Collector/time.html.twig | 59 + .../Collector/translations.html.twig | 85 ++ .../templates/Collector/user.html.twig | 63 + .../templates/Collector/views.html.twig | 58 + .../Profiler/webprofiler_dashboard.html.twig | 57 + .../Profiler/webprofiler_loader.html.twig | 73 ++ .../Profiler/webprofiler_panel.html.twig | 1 + .../Profiler/webprofiler_toolbar.css.twig | 311 +++++ .../Profiler/webprofiler_toolbar.html.twig | 35 + .../src/Functional/DataCollectorTest.php | 68 + .../src/FunctionalJavascript/ToolbarTest.php | 74 ++ .../WebprofilerTestBase.php | 60 + .../tests/src/Kernel/DecoratorTest.php | 65 + .../DataCollector/AssetsDataCollectorTest.php | 68 + .../DataCollector/CacheDataCollectorTest.php | 69 + .../DataCollector/DataCollectorBaseTest.php | 40 + .../devel/webprofiler/webprofiler.info.yml | 16 + .../devel/webprofiler/webprofiler.install | 186 +++ .../webprofiler/webprofiler.libraries.yml | 63 + .../webprofiler/webprofiler.links.menu.yml | 5 + .../webprofiler/webprofiler.links.task.yml | 9 + .../devel/webprofiler/webprofiler.make.yml | 12 + .../devel/webprofiler/webprofiler.module | 51 + .../webprofiler/webprofiler.permissions.yml | 7 + .../devel/webprofiler/webprofiler.routing.yml | 81 ++ .../webprofiler/webprofiler.services.yml | 262 ++++ 534 files changed, 50889 insertions(+), 26 deletions(-) delete mode 100644 config/sync/config_ignore.settings.yml create mode 100644 config/sync/simple_page_manager.page.home.yml create mode 100644 vendor/doctrine/cache/LICENSE create mode 100644 vendor/doctrine/cache/README.md create mode 100644 vendor/doctrine/cache/UPGRADE.md create mode 100644 vendor/doctrine/cache/composer.json create mode 100644 vendor/doctrine/cache/lib/Doctrine/Common/Cache/ApcCache.php create mode 100644 vendor/doctrine/cache/lib/Doctrine/Common/Cache/ApcuCache.php create mode 100644 vendor/doctrine/cache/lib/Doctrine/Common/Cache/ArrayCache.php create mode 100644 vendor/doctrine/cache/lib/Doctrine/Common/Cache/Cache.php create mode 100644 vendor/doctrine/cache/lib/Doctrine/Common/Cache/CacheProvider.php create mode 100644 vendor/doctrine/cache/lib/Doctrine/Common/Cache/ChainCache.php create mode 100644 vendor/doctrine/cache/lib/Doctrine/Common/Cache/ClearableCache.php create mode 100644 vendor/doctrine/cache/lib/Doctrine/Common/Cache/CouchbaseBucketCache.php create mode 100644 vendor/doctrine/cache/lib/Doctrine/Common/Cache/CouchbaseCache.php create mode 100644 vendor/doctrine/cache/lib/Doctrine/Common/Cache/ExtMongoDBCache.php create mode 100644 vendor/doctrine/cache/lib/Doctrine/Common/Cache/FileCache.php create mode 100644 vendor/doctrine/cache/lib/Doctrine/Common/Cache/FilesystemCache.php create mode 100644 vendor/doctrine/cache/lib/Doctrine/Common/Cache/FlushableCache.php create mode 100644 vendor/doctrine/cache/lib/Doctrine/Common/Cache/InvalidCacheId.php create mode 100644 vendor/doctrine/cache/lib/Doctrine/Common/Cache/LegacyMongoDBCache.php create mode 100644 vendor/doctrine/cache/lib/Doctrine/Common/Cache/MemcacheCache.php create mode 100644 vendor/doctrine/cache/lib/Doctrine/Common/Cache/MemcachedCache.php create mode 100644 vendor/doctrine/cache/lib/Doctrine/Common/Cache/MongoDBCache.php create mode 100644 vendor/doctrine/cache/lib/Doctrine/Common/Cache/MultiDeleteCache.php create mode 100644 vendor/doctrine/cache/lib/Doctrine/Common/Cache/MultiGetCache.php create mode 100644 vendor/doctrine/cache/lib/Doctrine/Common/Cache/MultiOperationCache.php create mode 100644 vendor/doctrine/cache/lib/Doctrine/Common/Cache/MultiPutCache.php create mode 100644 vendor/doctrine/cache/lib/Doctrine/Common/Cache/PhpFileCache.php create mode 100644 vendor/doctrine/cache/lib/Doctrine/Common/Cache/PredisCache.php create mode 100644 vendor/doctrine/cache/lib/Doctrine/Common/Cache/RedisCache.php create mode 100644 vendor/doctrine/cache/lib/Doctrine/Common/Cache/SQLite3Cache.php create mode 100644 vendor/doctrine/cache/lib/Doctrine/Common/Cache/Version.php create mode 100644 vendor/doctrine/cache/lib/Doctrine/Common/Cache/VoidCache.php create mode 100644 vendor/doctrine/cache/lib/Doctrine/Common/Cache/WinCacheCache.php create mode 100644 vendor/doctrine/cache/lib/Doctrine/Common/Cache/XcacheCache.php create mode 100644 vendor/doctrine/cache/lib/Doctrine/Common/Cache/ZendDataCache.php create mode 100644 vendor/doctrine/collections/.doctrine-project.json create mode 100644 vendor/doctrine/collections/CONTRIBUTING.md create mode 100644 vendor/doctrine/collections/LICENSE create mode 100644 vendor/doctrine/collections/README.md create mode 100644 vendor/doctrine/collections/composer.json create mode 100644 vendor/doctrine/collections/docs/en/derived-collections.rst create mode 100644 vendor/doctrine/collections/docs/en/expression-builder.rst create mode 100644 vendor/doctrine/collections/docs/en/expressions.rst create mode 100644 vendor/doctrine/collections/docs/en/index.rst create mode 100644 vendor/doctrine/collections/docs/en/lazy-collections.rst create mode 100644 vendor/doctrine/collections/docs/en/sidebar.rst create mode 100644 vendor/doctrine/collections/lib/Doctrine/Common/Collections/AbstractLazyCollection.php create mode 100644 vendor/doctrine/collections/lib/Doctrine/Common/Collections/ArrayCollection.php create mode 100644 vendor/doctrine/collections/lib/Doctrine/Common/Collections/Collection.php create mode 100644 vendor/doctrine/collections/lib/Doctrine/Common/Collections/Criteria.php create mode 100644 vendor/doctrine/collections/lib/Doctrine/Common/Collections/Expr/ClosureExpressionVisitor.php create mode 100644 vendor/doctrine/collections/lib/Doctrine/Common/Collections/Expr/Comparison.php create mode 100644 vendor/doctrine/collections/lib/Doctrine/Common/Collections/Expr/CompositeExpression.php create mode 100644 vendor/doctrine/collections/lib/Doctrine/Common/Collections/Expr/Expression.php create mode 100644 vendor/doctrine/collections/lib/Doctrine/Common/Collections/Expr/ExpressionVisitor.php create mode 100644 vendor/doctrine/collections/lib/Doctrine/Common/Collections/Expr/Value.php create mode 100644 vendor/doctrine/collections/lib/Doctrine/Common/Collections/ExpressionBuilder.php create mode 100644 vendor/doctrine/collections/lib/Doctrine/Common/Collections/Selectable.php create mode 100644 vendor/doctrine/collections/psalm.xml.dist create mode 100644 vendor/doctrine/common/.doctrine-project.json create mode 100644 vendor/doctrine/common/.github/FUNDING.yml create mode 100644 vendor/doctrine/common/LICENSE create mode 100644 vendor/doctrine/common/README.md create mode 100644 vendor/doctrine/common/UPGRADE_TO_2_1 create mode 100644 vendor/doctrine/common/UPGRADE_TO_2_2 create mode 100644 vendor/doctrine/common/composer.json create mode 100644 vendor/doctrine/common/docs/en/index.rst create mode 100644 vendor/doctrine/common/docs/en/reference/class-loading.rst create mode 100644 vendor/doctrine/common/humbug.json.dist create mode 100644 vendor/doctrine/common/lib/Doctrine/Common/ClassLoader.php create mode 100644 vendor/doctrine/common/lib/Doctrine/Common/CommonException.php create mode 100644 vendor/doctrine/common/lib/Doctrine/Common/Comparable.php create mode 100644 vendor/doctrine/common/lib/Doctrine/Common/Lexer.php create mode 100644 vendor/doctrine/common/lib/Doctrine/Common/Proxy/AbstractProxyFactory.php create mode 100644 vendor/doctrine/common/lib/Doctrine/Common/Proxy/Autoloader.php create mode 100644 vendor/doctrine/common/lib/Doctrine/Common/Proxy/Exception/InvalidArgumentException.php create mode 100644 vendor/doctrine/common/lib/Doctrine/Common/Proxy/Exception/OutOfBoundsException.php create mode 100644 vendor/doctrine/common/lib/Doctrine/Common/Proxy/Exception/ProxyException.php create mode 100644 vendor/doctrine/common/lib/Doctrine/Common/Proxy/Exception/UnexpectedValueException.php create mode 100644 vendor/doctrine/common/lib/Doctrine/Common/Proxy/Proxy.php create mode 100644 vendor/doctrine/common/lib/Doctrine/Common/Proxy/ProxyDefinition.php create mode 100644 vendor/doctrine/common/lib/Doctrine/Common/Proxy/ProxyGenerator.php create mode 100644 vendor/doctrine/common/lib/Doctrine/Common/Util/ClassUtils.php create mode 100644 vendor/doctrine/common/lib/Doctrine/Common/Util/Debug.php create mode 100644 vendor/doctrine/common/lib/Doctrine/Common/Util/Inflector.php create mode 100644 vendor/doctrine/common/lib/Doctrine/Common/Version.php create mode 100644 vendor/doctrine/common/phpstan.neon.dist create mode 100644 vendor/doctrine/event-manager/.doctrine-project.json create mode 100644 vendor/doctrine/event-manager/LICENSE create mode 100644 vendor/doctrine/event-manager/README.md create mode 100644 vendor/doctrine/event-manager/composer.json create mode 100644 vendor/doctrine/event-manager/lib/Doctrine/Common/EventArgs.php create mode 100644 vendor/doctrine/event-manager/lib/Doctrine/Common/EventManager.php create mode 100644 vendor/doctrine/event-manager/lib/Doctrine/Common/EventSubscriber.php create mode 100644 vendor/doctrine/inflector/LICENSE create mode 100644 vendor/doctrine/inflector/README.md create mode 100644 vendor/doctrine/inflector/composer.json create mode 100644 vendor/doctrine/inflector/docs/en/index.rst create mode 100644 vendor/doctrine/inflector/lib/Doctrine/Common/Inflector/Inflector.php create mode 100644 vendor/doctrine/inflector/lib/Doctrine/Inflector/CachedWordInflector.php create mode 100644 vendor/doctrine/inflector/lib/Doctrine/Inflector/GenericLanguageInflectorFactory.php create mode 100644 vendor/doctrine/inflector/lib/Doctrine/Inflector/Inflector.php create mode 100644 vendor/doctrine/inflector/lib/Doctrine/Inflector/InflectorFactory.php create mode 100644 vendor/doctrine/inflector/lib/Doctrine/Inflector/Language.php create mode 100644 vendor/doctrine/inflector/lib/Doctrine/Inflector/LanguageInflectorFactory.php create mode 100644 vendor/doctrine/inflector/lib/Doctrine/Inflector/NoopWordInflector.php create mode 100644 vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/English/Inflectible.php create mode 100644 vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/English/InflectorFactory.php create mode 100644 vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/English/Rules.php create mode 100644 vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/English/Uninflected.php create mode 100644 vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/French/Inflectible.php create mode 100644 vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/French/InflectorFactory.php create mode 100644 vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/French/Rules.php create mode 100644 vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/French/Uninflected.php create mode 100644 vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/NorwegianBokmal/Inflectible.php create mode 100644 vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/NorwegianBokmal/InflectorFactory.php create mode 100644 vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/NorwegianBokmal/Rules.php create mode 100644 vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/NorwegianBokmal/Uninflected.php create mode 100644 vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/Pattern.php create mode 100644 vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/Patterns.php create mode 100644 vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/Portuguese/Inflectible.php create mode 100644 vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/Portuguese/InflectorFactory.php create mode 100644 vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/Portuguese/Rules.php create mode 100644 vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/Portuguese/Uninflected.php create mode 100644 vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/Ruleset.php create mode 100644 vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/Spanish/Inflectible.php create mode 100644 vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/Spanish/InflectorFactory.php create mode 100644 vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/Spanish/Rules.php create mode 100644 vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/Spanish/Uninflected.php create mode 100644 vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/Substitution.php create mode 100644 vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/Substitutions.php create mode 100644 vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/Transformation.php create mode 100644 vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/Transformations.php create mode 100644 vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/Turkish/Inflectible.php create mode 100644 vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/Turkish/InflectorFactory.php create mode 100644 vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/Turkish/Rules.php create mode 100644 vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/Turkish/Uninflected.php create mode 100644 vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/Word.php create mode 100644 vendor/doctrine/inflector/lib/Doctrine/Inflector/RulesetInflector.php create mode 100644 vendor/doctrine/inflector/lib/Doctrine/Inflector/WordInflector.php create mode 100644 vendor/doctrine/inflector/phpstan.neon.dist create mode 100644 vendor/doctrine/persistence/.doctrine-project.json create mode 100644 vendor/doctrine/persistence/.github/workflows/continuous-integration.yml create mode 100644 vendor/doctrine/persistence/LICENSE create mode 100644 vendor/doctrine/persistence/README.md create mode 100644 vendor/doctrine/persistence/UPGRADE-1.2.md create mode 100644 vendor/doctrine/persistence/composer.json create mode 100644 vendor/doctrine/persistence/lib/Doctrine/Common/NotifyPropertyChanged.php create mode 100644 vendor/doctrine/persistence/lib/Doctrine/Common/Persistence/AbstractManagerRegistry.php create mode 100644 vendor/doctrine/persistence/lib/Doctrine/Common/Persistence/ConnectionRegistry.php create mode 100644 vendor/doctrine/persistence/lib/Doctrine/Common/Persistence/Event/LifecycleEventArgs.php create mode 100644 vendor/doctrine/persistence/lib/Doctrine/Common/Persistence/Event/LoadClassMetadataEventArgs.php create mode 100644 vendor/doctrine/persistence/lib/Doctrine/Common/Persistence/Event/ManagerEventArgs.php create mode 100644 vendor/doctrine/persistence/lib/Doctrine/Common/Persistence/Event/OnClearEventArgs.php create mode 100644 vendor/doctrine/persistence/lib/Doctrine/Common/Persistence/Event/PreUpdateEventArgs.php create mode 100644 vendor/doctrine/persistence/lib/Doctrine/Common/Persistence/ManagerRegistry.php create mode 100644 vendor/doctrine/persistence/lib/Doctrine/Common/Persistence/Mapping/AbstractClassMetadataFactory.php create mode 100644 vendor/doctrine/persistence/lib/Doctrine/Common/Persistence/Mapping/ClassMetadata.php create mode 100644 vendor/doctrine/persistence/lib/Doctrine/Common/Persistence/Mapping/ClassMetadataFactory.php create mode 100644 vendor/doctrine/persistence/lib/Doctrine/Common/Persistence/Mapping/Driver/AnnotationDriver.php create mode 100644 vendor/doctrine/persistence/lib/Doctrine/Common/Persistence/Mapping/Driver/DefaultFileLocator.php create mode 100644 vendor/doctrine/persistence/lib/Doctrine/Common/Persistence/Mapping/Driver/FileDriver.php create mode 100644 vendor/doctrine/persistence/lib/Doctrine/Common/Persistence/Mapping/Driver/FileLocator.php create mode 100644 vendor/doctrine/persistence/lib/Doctrine/Common/Persistence/Mapping/Driver/MappingDriver.php create mode 100644 vendor/doctrine/persistence/lib/Doctrine/Common/Persistence/Mapping/Driver/MappingDriverChain.php create mode 100644 vendor/doctrine/persistence/lib/Doctrine/Common/Persistence/Mapping/Driver/PHPDriver.php create mode 100644 vendor/doctrine/persistence/lib/Doctrine/Common/Persistence/Mapping/Driver/StaticPHPDriver.php create mode 100644 vendor/doctrine/persistence/lib/Doctrine/Common/Persistence/Mapping/Driver/SymfonyFileLocator.php create mode 100644 vendor/doctrine/persistence/lib/Doctrine/Common/Persistence/Mapping/MappingException.php create mode 100644 vendor/doctrine/persistence/lib/Doctrine/Common/Persistence/Mapping/ReflectionService.php create mode 100644 vendor/doctrine/persistence/lib/Doctrine/Common/Persistence/Mapping/RuntimeReflectionService.php create mode 100644 vendor/doctrine/persistence/lib/Doctrine/Common/Persistence/Mapping/StaticReflectionService.php create mode 100644 vendor/doctrine/persistence/lib/Doctrine/Common/Persistence/ObjectManager.php create mode 100644 vendor/doctrine/persistence/lib/Doctrine/Common/Persistence/ObjectManagerAware.php create mode 100644 vendor/doctrine/persistence/lib/Doctrine/Common/Persistence/ObjectManagerDecorator.php create mode 100644 vendor/doctrine/persistence/lib/Doctrine/Common/Persistence/ObjectRepository.php create mode 100644 vendor/doctrine/persistence/lib/Doctrine/Common/Persistence/PersistentObject.php create mode 100644 vendor/doctrine/persistence/lib/Doctrine/Common/Persistence/Proxy.php create mode 100644 vendor/doctrine/persistence/lib/Doctrine/Common/PropertyChangedListener.php create mode 100644 vendor/doctrine/persistence/lib/Doctrine/Persistence/AbstractManagerRegistry.php create mode 100644 vendor/doctrine/persistence/lib/Doctrine/Persistence/ConnectionRegistry.php create mode 100644 vendor/doctrine/persistence/lib/Doctrine/Persistence/Event/LifecycleEventArgs.php create mode 100644 vendor/doctrine/persistence/lib/Doctrine/Persistence/Event/LoadClassMetadataEventArgs.php create mode 100644 vendor/doctrine/persistence/lib/Doctrine/Persistence/Event/ManagerEventArgs.php create mode 100644 vendor/doctrine/persistence/lib/Doctrine/Persistence/Event/OnClearEventArgs.php create mode 100644 vendor/doctrine/persistence/lib/Doctrine/Persistence/Event/PreUpdateEventArgs.php create mode 100644 vendor/doctrine/persistence/lib/Doctrine/Persistence/ManagerRegistry.php create mode 100644 vendor/doctrine/persistence/lib/Doctrine/Persistence/Mapping/AbstractClassMetadataFactory.php create mode 100644 vendor/doctrine/persistence/lib/Doctrine/Persistence/Mapping/ClassMetadata.php create mode 100644 vendor/doctrine/persistence/lib/Doctrine/Persistence/Mapping/ClassMetadataFactory.php create mode 100644 vendor/doctrine/persistence/lib/Doctrine/Persistence/Mapping/Driver/AnnotationDriver.php create mode 100644 vendor/doctrine/persistence/lib/Doctrine/Persistence/Mapping/Driver/DefaultFileLocator.php create mode 100644 vendor/doctrine/persistence/lib/Doctrine/Persistence/Mapping/Driver/FileDriver.php create mode 100644 vendor/doctrine/persistence/lib/Doctrine/Persistence/Mapping/Driver/FileLocator.php create mode 100644 vendor/doctrine/persistence/lib/Doctrine/Persistence/Mapping/Driver/MappingDriver.php create mode 100644 vendor/doctrine/persistence/lib/Doctrine/Persistence/Mapping/Driver/MappingDriverChain.php create mode 100644 vendor/doctrine/persistence/lib/Doctrine/Persistence/Mapping/Driver/PHPDriver.php create mode 100644 vendor/doctrine/persistence/lib/Doctrine/Persistence/Mapping/Driver/StaticPHPDriver.php create mode 100644 vendor/doctrine/persistence/lib/Doctrine/Persistence/Mapping/Driver/SymfonyFileLocator.php create mode 100644 vendor/doctrine/persistence/lib/Doctrine/Persistence/Mapping/MappingException.php create mode 100644 vendor/doctrine/persistence/lib/Doctrine/Persistence/Mapping/ReflectionService.php create mode 100644 vendor/doctrine/persistence/lib/Doctrine/Persistence/Mapping/RuntimeReflectionService.php create mode 100644 vendor/doctrine/persistence/lib/Doctrine/Persistence/Mapping/StaticReflectionService.php create mode 100644 vendor/doctrine/persistence/lib/Doctrine/Persistence/NotifyPropertyChanged.php create mode 100644 vendor/doctrine/persistence/lib/Doctrine/Persistence/ObjectManager.php create mode 100644 vendor/doctrine/persistence/lib/Doctrine/Persistence/ObjectManagerAware.php create mode 100644 vendor/doctrine/persistence/lib/Doctrine/Persistence/ObjectManagerDecorator.php create mode 100644 vendor/doctrine/persistence/lib/Doctrine/Persistence/ObjectRepository.php create mode 100644 vendor/doctrine/persistence/lib/Doctrine/Persistence/PropertyChangedListener.php create mode 100644 vendor/doctrine/persistence/lib/Doctrine/Persistence/Proxy.php create mode 100644 vendor/doctrine/persistence/psalm.xml create mode 100644 web/modules/contrib/ckeditor_div_manager/LICENSE.txt create mode 100644 web/modules/contrib/ckeditor_div_manager/README.md create mode 100644 web/modules/contrib/ckeditor_div_manager/ckeditor_div_manager.info.yml create mode 100644 web/modules/contrib/ckeditor_div_manager/ckeditor_div_manager.install create mode 100644 web/modules/contrib/ckeditor_div_manager/composer.json create mode 100644 web/modules/contrib/ckeditor_div_manager/src/Plugin/CKEditorPlugin/CKEditorDiv.php create mode 100644 web/modules/contrib/devel/.docker/zz-php.ini create mode 100644 web/modules/contrib/devel/.gitignore create mode 100644 web/modules/contrib/devel/.gitlab-ci.yml create mode 100644 web/modules/contrib/devel/.travis.yml create mode 100644 web/modules/contrib/devel/CODEOWNERS create mode 100644 web/modules/contrib/devel/LICENSE.txt create mode 100644 web/modules/contrib/devel/README.md create mode 100644 web/modules/contrib/devel/composer.json create mode 100644 web/modules/contrib/devel/config/install/devel.settings.yml create mode 100644 web/modules/contrib/devel/config/install/devel.toolbar.settings.yml create mode 100644 web/modules/contrib/devel/config/install/system.menu.devel.yml create mode 100644 web/modules/contrib/devel/config/schema/devel.schema.yml create mode 100644 web/modules/contrib/devel/css/devel.css create mode 100644 web/modules/contrib/devel/css/devel.toolbar.css create mode 100644 web/modules/contrib/devel/devel.api.php create mode 100644 web/modules/contrib/devel/devel.info.yml create mode 100644 web/modules/contrib/devel/devel.install create mode 100644 web/modules/contrib/devel/devel.libraries.yml create mode 100644 web/modules/contrib/devel/devel.links.menu.yml create mode 100644 web/modules/contrib/devel/devel.links.task.yml create mode 100644 web/modules/contrib/devel/devel.module create mode 100644 web/modules/contrib/devel/devel.permissions.yml create mode 100644 web/modules/contrib/devel/devel.routing.yml create mode 100644 web/modules/contrib/devel/devel.services.yml create mode 100644 web/modules/contrib/devel/devel_generate/README.md create mode 100644 web/modules/contrib/devel/devel_generate/composer.json create mode 100644 web/modules/contrib/devel/devel_generate/devel_generate.batch.inc create mode 100644 web/modules/contrib/devel/devel_generate/devel_generate.info.yml create mode 100644 web/modules/contrib/devel/devel_generate/devel_generate.module create mode 100644 web/modules/contrib/devel/devel_generate/devel_generate.permissions.yml create mode 100644 web/modules/contrib/devel/devel_generate/devel_generate.routing.yml create mode 100644 web/modules/contrib/devel/devel_generate/devel_generate.services.yml create mode 100644 web/modules/contrib/devel/devel_generate/drush.services.yml create mode 100644 web/modules/contrib/devel/devel_generate/src/Annotation/DevelGenerate.php create mode 100644 web/modules/contrib/devel/devel_generate/src/Commands/DevelGenerateCommands.php create mode 100644 web/modules/contrib/devel/devel_generate/src/DevelGenerateBase.php create mode 100644 web/modules/contrib/devel/devel_generate/src/DevelGenerateBaseInterface.php create mode 100644 web/modules/contrib/devel/devel_generate/src/DevelGenerateException.php create mode 100644 web/modules/contrib/devel/devel_generate/src/DevelGeneratePermissions.php create mode 100644 web/modules/contrib/devel/devel_generate/src/DevelGeneratePluginManager.php create mode 100644 web/modules/contrib/devel/devel_generate/src/Form/DevelGenerateForm.php create mode 100644 web/modules/contrib/devel/devel_generate/src/Plugin/DevelGenerate/ContentDevelGenerate.php create mode 100644 web/modules/contrib/devel/devel_generate/src/Plugin/DevelGenerate/MediaDevelGenerate.php create mode 100644 web/modules/contrib/devel/devel_generate/src/Plugin/DevelGenerate/MenuDevelGenerate.php create mode 100644 web/modules/contrib/devel/devel_generate/src/Plugin/DevelGenerate/TermDevelGenerate.php create mode 100644 web/modules/contrib/devel/devel_generate/src/Plugin/DevelGenerate/UserDevelGenerate.php create mode 100644 web/modules/contrib/devel/devel_generate/src/Plugin/DevelGenerate/VocabularyDevelGenerate.php create mode 100644 web/modules/contrib/devel/devel_generate/src/Routing/DevelGenerateRoutes.php create mode 100644 web/modules/contrib/devel/devel_generate/tests/modules/devel_generate_example/devel_generate_example.info.yml create mode 100644 web/modules/contrib/devel/devel_generate/tests/modules/devel_generate_example/src/Plugin/DevelGenerate/ExampleDevelGenerate.php create mode 100644 web/modules/contrib/devel/devel_generate/tests/src/Functional/DevelGenerateBrowserTest.php create mode 100644 web/modules/contrib/devel/devel_generate/tests/src/Functional/DevelGenerateBrowserTestBase.php create mode 100644 web/modules/contrib/devel/devel_generate/tests/src/Functional/DevelGenerateCommandsTest.php create mode 100644 web/modules/contrib/devel/devel_generate/tests/src/Traits/DevelGenerateSetupTrait.php create mode 100644 web/modules/contrib/devel/devel_generate/tests/src/Unit/DevelGenerateManagerTest.php create mode 100644 web/modules/contrib/devel/drush.services.yml create mode 100644 web/modules/contrib/devel/icons/bebebe/cog.svg create mode 100644 web/modules/contrib/devel/icons/ffffff/cog.svg create mode 100644 web/modules/contrib/devel/icons/folder.png create mode 100644 web/modules/contrib/devel/phpcs.xml.dist create mode 100644 web/modules/contrib/devel/src/Annotation/DevelDumper.php create mode 100644 web/modules/contrib/devel/src/Commands/DevelCommands.php create mode 100644 web/modules/contrib/devel/src/Controller/ContainerInfoController.php create mode 100644 web/modules/contrib/devel/src/Controller/DevelController.php create mode 100644 web/modules/contrib/devel/src/Controller/ElementInfoController.php create mode 100644 web/modules/contrib/devel/src/Controller/EntityDebugController.php create mode 100644 web/modules/contrib/devel/src/Controller/EntityTypeInfoController.php create mode 100644 web/modules/contrib/devel/src/Controller/EventInfoController.php create mode 100644 web/modules/contrib/devel/src/Controller/LayoutInfoController.php create mode 100644 web/modules/contrib/devel/src/Controller/RouteInfoController.php create mode 100644 web/modules/contrib/devel/src/Controller/SwitchUserController.php create mode 100644 web/modules/contrib/devel/src/DevelDumperBase.php create mode 100644 web/modules/contrib/devel/src/DevelDumperInterface.php create mode 100644 web/modules/contrib/devel/src/DevelDumperManager.php create mode 100644 web/modules/contrib/devel/src/DevelDumperManagerInterface.php create mode 100644 web/modules/contrib/devel/src/DevelDumperPluginManager.php create mode 100644 web/modules/contrib/devel/src/DevelDumperPluginManagerInterface.php create mode 100644 web/modules/contrib/devel/src/Element/ClientSideFilterTable.php create mode 100644 web/modules/contrib/devel/src/EntityTypeInfo.php create mode 100644 web/modules/contrib/devel/src/EventSubscriber/ErrorHandlerSubscriber.php create mode 100644 web/modules/contrib/devel/src/EventSubscriber/ThemeInfoRebuildSubscriber.php create mode 100644 web/modules/contrib/devel/src/Form/ConfigEditor.php create mode 100644 web/modules/contrib/devel/src/Form/ConfigsList.php create mode 100644 web/modules/contrib/devel/src/Form/DevelReinstall.php create mode 100644 web/modules/contrib/devel/src/Form/RouterRebuildConfirmForm.php create mode 100644 web/modules/contrib/devel/src/Form/SettingsForm.php create mode 100644 web/modules/contrib/devel/src/Form/SwitchUserForm.php create mode 100644 web/modules/contrib/devel/src/Form/SystemStateEdit.php create mode 100644 web/modules/contrib/devel/src/Form/ToolbarSettingsForm.php create mode 100644 web/modules/contrib/devel/src/Plugin/Block/SwitchUserBlock.php create mode 100644 web/modules/contrib/devel/src/Plugin/Derivative/DevelLocalTask.php create mode 100644 web/modules/contrib/devel/src/Plugin/Devel/Dumper/DoctrineDebug.php create mode 100644 web/modules/contrib/devel/src/Plugin/Devel/Dumper/Kint.php create mode 100644 web/modules/contrib/devel/src/Plugin/Devel/Dumper/VarDumper.php create mode 100644 web/modules/contrib/devel/src/Plugin/Mail/DevelMailLog.php create mode 100644 web/modules/contrib/devel/src/Plugin/Menu/DestinationMenuLink.php create mode 100644 web/modules/contrib/devel/src/Plugin/Menu/RouteDetailMenuLink.php create mode 100644 web/modules/contrib/devel/src/Render/FilteredMarkup.php create mode 100644 web/modules/contrib/devel/src/Routing/RouteSubscriber.php create mode 100644 web/modules/contrib/devel/src/ToolbarHandler.php create mode 100644 web/modules/contrib/devel/src/Twig/Extension/Debug.php create mode 100644 web/modules/contrib/devel/tests/modules/devel_dumper_test/css/devel_dumper_test.css create mode 100644 web/modules/contrib/devel/tests/modules/devel_dumper_test/devel_dumper_test.info.yml create mode 100644 web/modules/contrib/devel/tests/modules/devel_dumper_test/devel_dumper_test.libraries.yml create mode 100644 web/modules/contrib/devel/tests/modules/devel_dumper_test/devel_dumper_test.routing.yml create mode 100644 web/modules/contrib/devel/tests/modules/devel_dumper_test/js/devel_dumper_test.js create mode 100644 web/modules/contrib/devel/tests/modules/devel_dumper_test/src/Controller/DumperTestController.php create mode 100644 web/modules/contrib/devel/tests/modules/devel_dumper_test/src/Plugin/Devel/Dumper/AvailableTestDumper.php create mode 100644 web/modules/contrib/devel/tests/modules/devel_dumper_test/src/Plugin/Devel/Dumper/NotAvailableTestDumper.php create mode 100644 web/modules/contrib/devel/tests/modules/devel_entity_test/devel_entity_test.info.yml create mode 100644 web/modules/contrib/devel/tests/modules/devel_entity_test/devel_entity_test.links.task.yml create mode 100644 web/modules/contrib/devel/tests/modules/devel_entity_test/devel_entity_test.module create mode 100644 web/modules/contrib/devel/tests/modules/devel_entity_test/src/Entity/DevelEntityTestCanonical.php create mode 100644 web/modules/contrib/devel/tests/modules/devel_entity_test/src/Entity/DevelEntityTestEdit.php create mode 100644 web/modules/contrib/devel/tests/modules/devel_entity_test/src/Entity/DevelEntityTestNoLinks.php create mode 100644 web/modules/contrib/devel/tests/modules/devel_entity_test/src/Plugin/Derivative/DevelEntityTestLocalTasks.php create mode 100644 web/modules/contrib/devel/tests/modules/devel_test/devel_test.info.yml create mode 100644 web/modules/contrib/devel/tests/modules/devel_test/devel_test.module create mode 100644 web/modules/contrib/devel/tests/modules/devel_test/devel_test.routing.yml create mode 100644 web/modules/contrib/devel/tests/modules/devel_test/devel_test.services.yml create mode 100644 web/modules/contrib/devel/tests/modules/devel_test/src/Controller/DevelTestController.php create mode 100644 web/modules/contrib/devel/tests/modules/devel_test/src/Routing/TestRouteSubscriber.php create mode 100644 web/modules/contrib/devel/tests/src/Functional/DevelBrowserTestBase.php create mode 100644 web/modules/contrib/devel/tests/src/Functional/DevelCommandsTest.php create mode 100644 web/modules/contrib/devel/tests/src/Functional/DevelContainerInfoTest.php create mode 100644 web/modules/contrib/devel/tests/src/Functional/DevelControllerTest.php create mode 100644 web/modules/contrib/devel/tests/src/Functional/DevelDumperTest.php create mode 100644 web/modules/contrib/devel/tests/src/Functional/DevelElementInfoTest.php create mode 100644 web/modules/contrib/devel/tests/src/Functional/DevelEntityTypeInfoTest.php create mode 100644 web/modules/contrib/devel/tests/src/Functional/DevelErrorHandlerTest.php create mode 100644 web/modules/contrib/devel/tests/src/Functional/DevelEventInfoTest.php create mode 100644 web/modules/contrib/devel/tests/src/Functional/DevelLayoutInfoTest.php create mode 100644 web/modules/contrib/devel/tests/src/Functional/DevelMenuLinksTest.php create mode 100644 web/modules/contrib/devel/tests/src/Functional/DevelModulesReinstallTest.php create mode 100644 web/modules/contrib/devel/tests/src/Functional/DevelRequirementsTest.php create mode 100644 web/modules/contrib/devel/tests/src/Functional/DevelRouteInfoTest.php create mode 100644 web/modules/contrib/devel/tests/src/Functional/DevelRouterRebuildTest.php create mode 100644 web/modules/contrib/devel/tests/src/Functional/DevelStateEditorTest.php create mode 100644 web/modules/contrib/devel/tests/src/Functional/DevelSwitchUserTest.php create mode 100644 web/modules/contrib/devel/tests/src/Functional/DevelToolbarTest.php create mode 100644 web/modules/contrib/devel/tests/src/Functional/DevelWebAssertHelper.php create mode 100644 web/modules/contrib/devel/tests/src/Kernel/DevelDumperTestTrait.php create mode 100644 web/modules/contrib/devel/tests/src/Kernel/DevelEnforcedDependenciesTest.php create mode 100644 web/modules/contrib/devel/tests/src/Kernel/DevelMailLogTest.php create mode 100644 web/modules/contrib/devel/tests/src/Kernel/DevelQueryDebugTest.php create mode 100644 web/modules/contrib/devel/tests/src/Kernel/DevelTwigExtensionTest.php create mode 100644 web/modules/contrib/devel/tests/src/Unit/DevelClientSideFilterTableTest.php create mode 100644 web/modules/contrib/devel/webprofiler/README.md create mode 100644 web/modules/contrib/devel/webprofiler/composer.json create mode 100644 web/modules/contrib/devel/webprofiler/config/install/webprofiler.config.yml create mode 100644 web/modules/contrib/devel/webprofiler/config/schema/webprofiler.schema.yml create mode 100644 web/modules/contrib/devel/webprofiler/console.services.yml create mode 100644 web/modules/contrib/devel/webprofiler/console/translations/en/webprofiler.benchmark.yml create mode 100644 web/modules/contrib/devel/webprofiler/console/translations/en/webprofiler.export.yml create mode 100644 web/modules/contrib/devel/webprofiler/console/translations/en/webprofiler.list.yml create mode 100644 web/modules/contrib/devel/webprofiler/console/translations/es/webprofiler.benchmark.yml create mode 100644 web/modules/contrib/devel/webprofiler/console/translations/es/webprofiler.export.yml create mode 100644 web/modules/contrib/devel/webprofiler/console/translations/es/webprofiler.list.yml create mode 100644 web/modules/contrib/devel/webprofiler/css/app/dashboard.css create mode 100644 web/modules/contrib/devel/webprofiler/css/timeline.css create mode 100644 web/modules/contrib/devel/webprofiler/images/caret-down.svg create mode 100644 web/modules/contrib/devel/webprofiler/js/app/collections/collectors.js create mode 100644 web/modules/contrib/devel/webprofiler/js/app/helpers.js create mode 100644 web/modules/contrib/devel/webprofiler/js/app/main.js create mode 100644 web/modules/contrib/devel/webprofiler/js/app/models/collector.js create mode 100644 web/modules/contrib/devel/webprofiler/js/app/routers/collectors.js create mode 100644 web/modules/contrib/devel/webprofiler/js/app/views/collector.js create mode 100644 web/modules/contrib/devel/webprofiler/js/app/views/collectorsList.js create mode 100644 web/modules/contrib/devel/webprofiler/js/app/views/details.js create mode 100644 web/modules/contrib/devel/webprofiler/js/app/views/layout.js create mode 100644 web/modules/contrib/devel/webprofiler/js/database.js create mode 100644 web/modules/contrib/devel/webprofiler/js/timeline.js create mode 100644 web/modules/contrib/devel/webprofiler/src/Access/AccessManagerWrapper.php create mode 100644 web/modules/contrib/devel/webprofiler/src/Asset/CssCollectionRendererWrapper.php create mode 100644 web/modules/contrib/devel/webprofiler/src/Asset/JsCollectionRendererWrapper.php create mode 100644 web/modules/contrib/devel/webprofiler/src/Cache/CacheBackendWrapper.php create mode 100644 web/modules/contrib/devel/webprofiler/src/Cache/CacheFactoryWrapper.php create mode 100644 web/modules/contrib/devel/webprofiler/src/Command/BenchmarkCommand.php create mode 100644 web/modules/contrib/devel/webprofiler/src/Command/BenchmarkData.php create mode 100644 web/modules/contrib/devel/webprofiler/src/Command/ExportCommand.php create mode 100644 web/modules/contrib/devel/webprofiler/src/Command/ListCommand.php create mode 100644 web/modules/contrib/devel/webprofiler/src/Compiler/DecoratorPass.php create mode 100644 web/modules/contrib/devel/webprofiler/src/Compiler/ProfilerPass.php create mode 100644 web/modules/contrib/devel/webprofiler/src/Compiler/ServicePass.php create mode 100644 web/modules/contrib/devel/webprofiler/src/Compiler/StoragePass.php create mode 100644 web/modules/contrib/devel/webprofiler/src/Config/ConfigFactoryWrapper.php create mode 100644 web/modules/contrib/devel/webprofiler/src/Controller/DashboardController.php create mode 100644 web/modules/contrib/devel/webprofiler/src/Controller/DatabaseController.php create mode 100644 web/modules/contrib/devel/webprofiler/src/Controller/ToolbarController.php create mode 100644 web/modules/contrib/devel/webprofiler/src/DataCollector/AssetsDataCollector.php create mode 100644 web/modules/contrib/devel/webprofiler/src/DataCollector/BlocksDataCollector.php create mode 100644 web/modules/contrib/devel/webprofiler/src/DataCollector/CacheDataCollector.php create mode 100644 web/modules/contrib/devel/webprofiler/src/DataCollector/ConfigDataCollector.php create mode 100644 web/modules/contrib/devel/webprofiler/src/DataCollector/DatabaseDataCollector.php create mode 100644 web/modules/contrib/devel/webprofiler/src/DataCollector/DevelDataCollector.php create mode 100644 web/modules/contrib/devel/webprofiler/src/DataCollector/DrupalDataCollector.php create mode 100644 web/modules/contrib/devel/webprofiler/src/DataCollector/DrupalDataCollectorTrait.php create mode 100644 web/modules/contrib/devel/webprofiler/src/DataCollector/EventsDataCollector.php create mode 100644 web/modules/contrib/devel/webprofiler/src/DataCollector/ExtensionDataCollector.php create mode 100644 web/modules/contrib/devel/webprofiler/src/DataCollector/FormsDataCollector.php create mode 100644 web/modules/contrib/devel/webprofiler/src/DataCollector/HttpDataCollector.php create mode 100644 web/modules/contrib/devel/webprofiler/src/DataCollector/MailDataCollector.php create mode 100644 web/modules/contrib/devel/webprofiler/src/DataCollector/PerformanceTimingDataCollector.php create mode 100644 web/modules/contrib/devel/webprofiler/src/DataCollector/PhpConfigDataCollector.php create mode 100644 web/modules/contrib/devel/webprofiler/src/DataCollector/RequestDataCollector.php create mode 100644 web/modules/contrib/devel/webprofiler/src/DataCollector/RoutingDataCollector.php create mode 100644 web/modules/contrib/devel/webprofiler/src/DataCollector/ServicesDataCollector.php create mode 100644 web/modules/contrib/devel/webprofiler/src/DataCollector/StateDataCollector.php create mode 100644 web/modules/contrib/devel/webprofiler/src/DataCollector/ThemeDataCollector.php create mode 100644 web/modules/contrib/devel/webprofiler/src/DataCollector/TimeDataCollector.php create mode 100644 web/modules/contrib/devel/webprofiler/src/DataCollector/TranslationsDataCollector.php create mode 100644 web/modules/contrib/devel/webprofiler/src/DataCollector/UserDataCollector.php create mode 100644 web/modules/contrib/devel/webprofiler/src/DataCollector/ViewsDataCollector.php create mode 100644 web/modules/contrib/devel/webprofiler/src/Decorator.php create mode 100644 web/modules/contrib/devel/webprofiler/src/DependencyInjection/TraceableContainer.php create mode 100644 web/modules/contrib/devel/webprofiler/src/DrupalDataCollectorInterface.php create mode 100644 web/modules/contrib/devel/webprofiler/src/Entity/Decorators/Config/ConfigEntityStorageDecorator.php create mode 100644 web/modules/contrib/devel/webprofiler/src/Entity/Decorators/Config/DomainStorageDecorator.php create mode 100644 web/modules/contrib/devel/webprofiler/src/Entity/Decorators/Config/ImageStyleStorageDecorator.php create mode 100644 web/modules/contrib/devel/webprofiler/src/Entity/Decorators/Config/RoleStorageDecorator.php create mode 100644 web/modules/contrib/devel/webprofiler/src/Entity/Decorators/Config/ShortcutSetStorageDecorator.php create mode 100644 web/modules/contrib/devel/webprofiler/src/Entity/Decorators/Config/VocabularyStorageDecorator.php create mode 100644 web/modules/contrib/devel/webprofiler/src/Entity/EntityDecorator.php create mode 100644 web/modules/contrib/devel/webprofiler/src/Entity/EntityManagerWrapper.php create mode 100644 web/modules/contrib/devel/webprofiler/src/Entity/EntityViewBuilderDecorator.php create mode 100644 web/modules/contrib/devel/webprofiler/src/EventDispatcher/EventDispatcherTraceableInterface.php create mode 100644 web/modules/contrib/devel/webprofiler/src/EventDispatcher/TraceableEventDispatcher.php create mode 100644 web/modules/contrib/devel/webprofiler/src/EventSubscriber/ProfilerSubscriber.php create mode 100644 web/modules/contrib/devel/webprofiler/src/EventSubscriber/WebprofilerEventSubscriber.php create mode 100644 web/modules/contrib/devel/webprofiler/src/Form/ConfigForm.php create mode 100644 web/modules/contrib/devel/webprofiler/src/Form/FormBuilderWrapper.php create mode 100644 web/modules/contrib/devel/webprofiler/src/Form/ProfilesFilterForm.php create mode 100644 web/modules/contrib/devel/webprofiler/src/Frontend/PerformanceTimingData.php create mode 100644 web/modules/contrib/devel/webprofiler/src/Helper/ClassShortener.php create mode 100644 web/modules/contrib/devel/webprofiler/src/Helper/ClassShortenerInterface.php create mode 100644 web/modules/contrib/devel/webprofiler/src/Helper/IdeLinkGenerator.php create mode 100644 web/modules/contrib/devel/webprofiler/src/Helper/IdeLinkGeneratorInterface.php create mode 100644 web/modules/contrib/devel/webprofiler/src/Http/HttpClientMiddleware.php create mode 100644 web/modules/contrib/devel/webprofiler/src/Mail/MailManagerWrapper.php create mode 100644 web/modules/contrib/devel/webprofiler/src/Profiler/DatabaseProfilerStorage.php create mode 100644 web/modules/contrib/devel/webprofiler/src/Profiler/FileProfilerStorage.php create mode 100644 web/modules/contrib/devel/webprofiler/src/Profiler/Profiler.php create mode 100644 web/modules/contrib/devel/webprofiler/src/Profiler/ProfilerStorageFactory.php create mode 100644 web/modules/contrib/devel/webprofiler/src/Profiler/ProfilerStorageManager.php create mode 100644 web/modules/contrib/devel/webprofiler/src/Profiler/TemplateManager.php create mode 100644 web/modules/contrib/devel/webprofiler/src/ProxyClass/Command/ListCommand.php create mode 100644 web/modules/contrib/devel/webprofiler/src/RequestMatcher/WebprofilerRequestMatcher.php create mode 100644 web/modules/contrib/devel/webprofiler/src/Routing/TokenConverter.php create mode 100644 web/modules/contrib/devel/webprofiler/src/StackMiddleware/WebprofilerMiddleware.php create mode 100644 web/modules/contrib/devel/webprofiler/src/State/StateWrapper.php create mode 100644 web/modules/contrib/devel/webprofiler/src/Stopwatch.php create mode 100644 web/modules/contrib/devel/webprofiler/src/StringTranslation/TranslationManagerWrapper.php create mode 100644 web/modules/contrib/devel/webprofiler/src/Theme/ThemeNegotiatorWrapper.php create mode 100644 web/modules/contrib/devel/webprofiler/src/Twig/Dumper/HtmlDumper.php create mode 100644 web/modules/contrib/devel/webprofiler/src/Twig/Extension/ProfilerExtension.php create mode 100644 web/modules/contrib/devel/webprofiler/src/Views/TraceableViewExecutable.php create mode 100644 web/modules/contrib/devel/webprofiler/src/Views/ViewExecutableFactoryWrapper.php create mode 100644 web/modules/contrib/devel/webprofiler/src/WebprofilerServiceProvider.php create mode 100644 web/modules/contrib/devel/webprofiler/templates/Collector/assets.html.twig create mode 100644 web/modules/contrib/devel/webprofiler/templates/Collector/blocks.html.twig create mode 100644 web/modules/contrib/devel/webprofiler/templates/Collector/cache.html.twig create mode 100644 web/modules/contrib/devel/webprofiler/templates/Collector/config.html.twig create mode 100644 web/modules/contrib/devel/webprofiler/templates/Collector/database.html.twig create mode 100644 web/modules/contrib/devel/webprofiler/templates/Collector/devel.html.twig create mode 100644 web/modules/contrib/devel/webprofiler/templates/Collector/drupal.html.twig create mode 100644 web/modules/contrib/devel/webprofiler/templates/Collector/events.html.twig create mode 100644 web/modules/contrib/devel/webprofiler/templates/Collector/extensions.html.twig create mode 100644 web/modules/contrib/devel/webprofiler/templates/Collector/forms.html.twig create mode 100644 web/modules/contrib/devel/webprofiler/templates/Collector/http.html.twig create mode 100644 web/modules/contrib/devel/webprofiler/templates/Collector/mail.html.twig create mode 100644 web/modules/contrib/devel/webprofiler/templates/Collector/performance_timing.html.twig create mode 100644 web/modules/contrib/devel/webprofiler/templates/Collector/php_config.html.twig create mode 100644 web/modules/contrib/devel/webprofiler/templates/Collector/request.html.twig create mode 100644 web/modules/contrib/devel/webprofiler/templates/Collector/routing.html.twig create mode 100644 web/modules/contrib/devel/webprofiler/templates/Collector/services.html.twig create mode 100644 web/modules/contrib/devel/webprofiler/templates/Collector/state.html.twig create mode 100644 web/modules/contrib/devel/webprofiler/templates/Collector/theme.html.twig create mode 100644 web/modules/contrib/devel/webprofiler/templates/Collector/time.html.twig create mode 100644 web/modules/contrib/devel/webprofiler/templates/Collector/translations.html.twig create mode 100644 web/modules/contrib/devel/webprofiler/templates/Collector/user.html.twig create mode 100644 web/modules/contrib/devel/webprofiler/templates/Collector/views.html.twig create mode 100644 web/modules/contrib/devel/webprofiler/templates/Profiler/webprofiler_dashboard.html.twig create mode 100644 web/modules/contrib/devel/webprofiler/templates/Profiler/webprofiler_loader.html.twig create mode 100644 web/modules/contrib/devel/webprofiler/templates/Profiler/webprofiler_panel.html.twig create mode 100644 web/modules/contrib/devel/webprofiler/templates/Profiler/webprofiler_toolbar.css.twig create mode 100644 web/modules/contrib/devel/webprofiler/templates/Profiler/webprofiler_toolbar.html.twig create mode 100644 web/modules/contrib/devel/webprofiler/tests/src/Functional/DataCollectorTest.php create mode 100644 web/modules/contrib/devel/webprofiler/tests/src/FunctionalJavascript/ToolbarTest.php create mode 100644 web/modules/contrib/devel/webprofiler/tests/src/FunctionalJavascript/WebprofilerTestBase.php create mode 100644 web/modules/contrib/devel/webprofiler/tests/src/Kernel/DecoratorTest.php create mode 100644 web/modules/contrib/devel/webprofiler/tests/src/Unit/DataCollector/AssetsDataCollectorTest.php create mode 100644 web/modules/contrib/devel/webprofiler/tests/src/Unit/DataCollector/CacheDataCollectorTest.php create mode 100644 web/modules/contrib/devel/webprofiler/tests/src/Unit/DataCollector/DataCollectorBaseTest.php create mode 100644 web/modules/contrib/devel/webprofiler/webprofiler.info.yml create mode 100644 web/modules/contrib/devel/webprofiler/webprofiler.install create mode 100644 web/modules/contrib/devel/webprofiler/webprofiler.libraries.yml create mode 100644 web/modules/contrib/devel/webprofiler/webprofiler.links.menu.yml create mode 100644 web/modules/contrib/devel/webprofiler/webprofiler.links.task.yml create mode 100644 web/modules/contrib/devel/webprofiler/webprofiler.make.yml create mode 100644 web/modules/contrib/devel/webprofiler/webprofiler.module create mode 100644 web/modules/contrib/devel/webprofiler/webprofiler.permissions.yml create mode 100644 web/modules/contrib/devel/webprofiler/webprofiler.routing.yml create mode 100644 web/modules/contrib/devel/webprofiler/webprofiler.services.yml diff --git a/composer.json b/composer.json index fd28764b9..124d6c264 100644 --- a/composer.json +++ b/composer.json @@ -19,12 +19,14 @@ "drupal/admin_toolbar": "^2.4", "drupal/backup_migrate": "^5", "drupal/bootstrap_barrio": "^5.1", + "drupal/ckeditor_div_manager": "^1.1", "drupal/config_ignore": "^2.3", "drupal/config_split": "^2", "drupal/core-composer-scaffold": "^9.1", "drupal/core-project-message": "^9.1", "drupal/core-recommended": "^9.1", "drupal/custom_markup_block": "^1.1", + "drupal/devel": "^4.1", "drupal/fences": "^2.0@RC", "drupal/profile_switcher": "^1.0@alpha", "drupal/simple_page_manager": "^1.0@alpha", diff --git a/composer.lock b/composer.lock index 751fa37cf..cfa041bea 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "bed3ec4a530cc1567cebd40ce9770c5d", + "content-hash": "af5cf1eb112875f8814b8a7529fdcc42", "packages": [ { "name": "asm89/stack-cors", @@ -1146,6 +1146,466 @@ }, "time": "2020-10-26T10:28:16+00:00" }, + { + "name": "doctrine/cache", + "version": "1.10.2", + "source": { + "type": "git", + "url": "https://github.com/doctrine/cache.git", + "reference": "13e3381b25847283a91948d04640543941309727" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/cache/zipball/13e3381b25847283a91948d04640543941309727", + "reference": "13e3381b25847283a91948d04640543941309727", + "shasum": "" + }, + "require": { + "php": "~7.1 || ^8.0" + }, + "conflict": { + "doctrine/common": ">2.2,<2.4" + }, + "require-dev": { + "alcaeus/mongo-php-adapter": "^1.1", + "doctrine/coding-standard": "^6.0", + "mongodb/mongodb": "^1.1", + "phpunit/phpunit": "^7.0", + "predis/predis": "~1.0" + }, + "suggest": { + "alcaeus/mongo-php-adapter": "Required to use legacy MongoDB driver" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.9.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Common\\Cache\\": "lib/Doctrine/Common/Cache" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "PHP Doctrine Cache library is a popular cache implementation that supports many different drivers such as redis, memcache, apc, mongodb and others.", + "homepage": "https://www.doctrine-project.org/projects/cache.html", + "keywords": [ + "abstraction", + "apcu", + "cache", + "caching", + "couchdb", + "memcached", + "php", + "redis", + "xcache" + ], + "support": { + "issues": "https://github.com/doctrine/cache/issues", + "source": "https://github.com/doctrine/cache/tree/1.10.x" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fcache", + "type": "tidelift" + } + ], + "time": "2020-07-07T18:54:01+00:00" + }, + { + "name": "doctrine/collections", + "version": "1.6.7", + "source": { + "type": "git", + "url": "https://github.com/doctrine/collections.git", + "reference": "55f8b799269a1a472457bd1a41b4f379d4cfba4a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/collections/zipball/55f8b799269a1a472457bd1a41b4f379d4cfba4a", + "reference": "55f8b799269a1a472457bd1a41b4f379d4cfba4a", + "shasum": "" + }, + "require": { + "php": "^7.1.3 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^6.0", + "phpstan/phpstan-shim": "^0.9.2", + "phpunit/phpunit": "^7.0", + "vimeo/psalm": "^3.8.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Common\\Collections\\": "lib/Doctrine/Common/Collections" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "PHP Doctrine Collections library that adds additional functionality on top of PHP arrays.", + "homepage": "https://www.doctrine-project.org/projects/collections.html", + "keywords": [ + "array", + "collections", + "iterators", + "php" + ], + "support": { + "issues": "https://github.com/doctrine/collections/issues", + "source": "https://github.com/doctrine/collections/tree/1.6.7" + }, + "time": "2020-07-27T17:53:49+00:00" + }, + { + "name": "doctrine/common", + "version": "2.13.3", + "source": { + "type": "git", + "url": "https://github.com/doctrine/common.git", + "reference": "f3812c026e557892c34ef37f6ab808a6b567da7f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/common/zipball/f3812c026e557892c34ef37f6ab808a6b567da7f", + "reference": "f3812c026e557892c34ef37f6ab808a6b567da7f", + "shasum": "" + }, + "require": { + "doctrine/annotations": "^1.0", + "doctrine/cache": "^1.0", + "doctrine/collections": "^1.0", + "doctrine/event-manager": "^1.0", + "doctrine/inflector": "^1.0", + "doctrine/lexer": "^1.0", + "doctrine/persistence": "^1.3.3", + "doctrine/reflection": "^1.0", + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^1.0", + "phpstan/phpstan": "^0.11", + "phpstan/phpstan-phpunit": "^0.11", + "phpunit/phpunit": "^7.0", + "squizlabs/php_codesniffer": "^3.0", + "symfony/phpunit-bridge": "^4.0.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.11.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Common\\": "lib/Doctrine/Common" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + }, + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com" + } + ], + "description": "PHP Doctrine Common project is a library that provides additional functionality that other Doctrine projects depend on such as better reflection support, persistence interfaces, proxies, event system and much more.", + "homepage": "https://www.doctrine-project.org/projects/common.html", + "keywords": [ + "common", + "doctrine", + "php" + ], + "support": { + "issues": "https://github.com/doctrine/common/issues", + "source": "https://github.com/doctrine/common/tree/2.13.x" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fcommon", + "type": "tidelift" + } + ], + "time": "2020-06-05T16:46:05+00:00" + }, + { + "name": "doctrine/event-manager", + "version": "1.1.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/event-manager.git", + "reference": "41370af6a30faa9dc0368c4a6814d596e81aba7f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/event-manager/zipball/41370af6a30faa9dc0368c4a6814d596e81aba7f", + "reference": "41370af6a30faa9dc0368c4a6814d596e81aba7f", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "conflict": { + "doctrine/common": "<2.9@dev" + }, + "require-dev": { + "doctrine/coding-standard": "^6.0", + "phpunit/phpunit": "^7.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Common\\": "lib/Doctrine/Common" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + }, + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com" + } + ], + "description": "The Doctrine Event Manager is a simple PHP event system that was built to be used with the various Doctrine projects.", + "homepage": "https://www.doctrine-project.org/projects/event-manager.html", + "keywords": [ + "event", + "event dispatcher", + "event manager", + "event system", + "events" + ], + "support": { + "issues": "https://github.com/doctrine/event-manager/issues", + "source": "https://github.com/doctrine/event-manager/tree/1.1.x" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fevent-manager", + "type": "tidelift" + } + ], + "time": "2020-05-29T18:28:51+00:00" + }, + { + "name": "doctrine/inflector", + "version": "1.4.3", + "source": { + "type": "git", + "url": "https://github.com/doctrine/inflector.git", + "reference": "4650c8b30c753a76bf44fb2ed00117d6f367490c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/inflector/zipball/4650c8b30c753a76bf44fb2ed00117d6f367490c", + "reference": "4650c8b30c753a76bf44fb2ed00117d6f367490c", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^7.0", + "phpstan/phpstan": "^0.11", + "phpstan/phpstan-phpunit": "^0.11", + "phpstan/phpstan-strict-rules": "^0.11", + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Common\\Inflector\\": "lib/Doctrine/Common/Inflector", + "Doctrine\\Inflector\\": "lib/Doctrine/Inflector" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "PHP Doctrine Inflector is a small library that can perform string manipulations with regard to upper/lowercase and singular/plural forms of words.", + "homepage": "https://www.doctrine-project.org/projects/inflector.html", + "keywords": [ + "inflection", + "inflector", + "lowercase", + "manipulation", + "php", + "plural", + "singular", + "strings", + "uppercase", + "words" + ], + "support": { + "issues": "https://github.com/doctrine/inflector/issues", + "source": "https://github.com/doctrine/inflector/tree/1.4.x" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finflector", + "type": "tidelift" + } + ], + "time": "2020-05-29T07:19:59+00:00" + }, { "name": "doctrine/lexer", "version": "1.2.1", @@ -1226,6 +1686,108 @@ ], "time": "2020-05-25T17:44:05+00:00" }, + { + "name": "doctrine/persistence", + "version": "1.3.8", + "source": { + "type": "git", + "url": "https://github.com/doctrine/persistence.git", + "reference": "7a6eac9fb6f61bba91328f15aa7547f4806ca288" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/persistence/zipball/7a6eac9fb6f61bba91328f15aa7547f4806ca288", + "reference": "7a6eac9fb6f61bba91328f15aa7547f4806ca288", + "shasum": "" + }, + "require": { + "doctrine/annotations": "^1.0", + "doctrine/cache": "^1.0", + "doctrine/collections": "^1.0", + "doctrine/event-manager": "^1.0", + "doctrine/reflection": "^1.2", + "php": "^7.1 || ^8.0" + }, + "conflict": { + "doctrine/common": "<2.10@dev" + }, + "require-dev": { + "doctrine/coding-standard": "^6.0", + "phpstan/phpstan": "^0.11", + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0", + "vimeo/psalm": "^3.11" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Common\\": "lib/Doctrine/Common", + "Doctrine\\Persistence\\": "lib/Doctrine/Persistence" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + }, + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com" + } + ], + "description": "The Doctrine Persistence project is a set of shared interfaces and functionality that the different Doctrine object mappers share.", + "homepage": "https://doctrine-project.org/projects/persistence.html", + "keywords": [ + "mapper", + "object", + "odm", + "orm", + "persistence" + ], + "support": { + "issues": "https://github.com/doctrine/persistence/issues", + "source": "https://github.com/doctrine/persistence/tree/1.3.x" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fpersistence", + "type": "tidelift" + } + ], + "time": "2020-06-20T12:56:16+00:00" + }, { "name": "doctrine/reflection", "version": "1.2.2", @@ -1503,6 +2065,56 @@ "source": "https://git.drupalcode.org/project/bootstrap_barrio" } }, + { + "name": "drupal/ckeditor_div_manager", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://git.drupalcode.org/project/ckeditor_div_manager.git", + "reference": "8.x-1.1" + }, + "dist": { + "type": "zip", + "url": "https://ftp.drupal.org/files/projects/ckeditor_div_manager-8.x-1.1.zip", + "reference": "8.x-1.1", + "shasum": "2aba67e132e0d54886e8bdc9914f51a6160d3875" + }, + "require": { + "drupal/core": "^8 || ^9" + }, + "type": "drupal-module", + "extra": { + "drupal": { + "version": "8.x-1.1", + "datestamp": "1615574655", + "security-coverage": { + "status": "covered", + "message": "Covered by Drupal's security advisory policy" + } + } + }, + "notification-url": "https://packages.drupal.org/8/downloads", + "license": [ + "GPL-2.0+" + ], + "authors": [ + { + "name": "Corey Eiseman", + "homepage": "https://www.drupal.org/u/toegristle", + "role": "Maintainer" + }, + { + "name": "toegristle", + "homepage": "https://www.drupal.org/user/1802910" + } + ], + "description": "Adds the Div Container Manager plugin to CKEditor.", + "homepage": "https://www.drupal.org/project/ckeditor_div_manager", + "support": { + "source": "https://cgit.drupalcode.org/ckeditor_div_manager", + "issues": "https://www.drupal.org/project/issues/ckeditor_div_manager" + } + }, { "name": "drupal/config_filter", "version": "2.2.0", @@ -2196,6 +2808,72 @@ "irc": "irc://irc.freenode.org/drupal-contribute" } }, + { + "name": "drupal/devel", + "version": "4.1.1", + "source": { + "type": "git", + "url": "https://git.drupalcode.org/project/devel.git", + "reference": "4.1.1" + }, + "dist": { + "type": "zip", + "url": "https://ftp.drupal.org/files/projects/devel-4.1.1.zip", + "reference": "4.1.1", + "shasum": "88e5d49dda26a3136291ecd97bc6c8e897b24198" + }, + "require": { + "doctrine/common": "^2.7", + "drupal/core": "^8.8 || ^9", + "symfony/var-dumper": "^4 || ^5" + }, + "conflict": { + "kint-php/kint": "<3" + }, + "require-dev": { + "drush/drush": "^10" + }, + "suggest": { + "kint-php/kint": "Kint provides an informative display of arrays/objects. Useful for debugging and developing." + }, + "type": "drupal-module", + "extra": { + "drupal": { + "version": "4.1.1", + "datestamp": "1609419527", + "security-coverage": { + "status": "covered", + "message": "Covered by Drupal's security advisory policy" + } + }, + "drush": { + "services": { + "drush.services.yml": "^9 || ^10" + } + } + }, + "notification-url": "https://packages.drupal.org/8/downloads", + "license": [ + "GPL-2.0-or-later" + ], + "authors": [ + { + "name": "drupalspoons", + "homepage": "https://www.drupal.org/user/3647684" + }, + { + "name": "moshe weitzman", + "homepage": "https://www.drupal.org/user/23" + } + ], + "description": "Various blocks, pages, and functions for developers.", + "homepage": "https://www.drupal.org/project/devel", + "support": { + "source": "https://gitlab.com/drupalspoons/devel", + "issues": "https://gitlab.com/drupalspoons/devel/-/issues", + "slack": "https://drupal.slack.com/archives/C012WAW1MH6" + } + }, { "name": "drupal/fences", "version": "2.0.0-rc1", diff --git a/config/sync/block.block.democraticli_page_title.yml b/config/sync/block.block.democraticli_page_title.yml index cdbd579e7..1c82eda60 100644 --- a/config/sync/block.block.democraticli_page_title.yml +++ b/config/sync/block.block.democraticli_page_title.yml @@ -3,7 +3,7 @@ langcode: en status: true dependencies: module: - - node + - system theme: - democraticli _core: @@ -20,12 +20,7 @@ settings: provider: core label_display: '0' visibility: - node_type: - id: node_type - bundles: - article: article - page: page - position: position - negate: false - context_mapping: - node: '@node.node_route_context:node' + request_path: + id: request_path + pages: '' + negate: true diff --git a/config/sync/config_ignore.settings.yml b/config/sync/config_ignore.settings.yml deleted file mode 100644 index 4f4f4b792..000000000 --- a/config/sync/config_ignore.settings.yml +++ /dev/null @@ -1,4 +0,0 @@ -ignored_config_entities: - - system.site -_core: - default_config_hash: UVH1aJ4b44UM-VdPVN7hNNuuVqfReJxwfVeDQH1Hvsk diff --git a/config/sync/core.extension.yml b/config/sync/core.extension.yml index f7f40270c..f257d0390 100644 --- a/config/sync/core.extension.yml +++ b/config/sync/core.extension.yml @@ -12,9 +12,9 @@ module: comment: 0 config: 0 config_filter: 0 - config_ignore: 0 contact: 0 contextual: 0 + custom_markup_block: 0 datetime: 0 dblog: 0 democraticli_custom: 0 @@ -42,6 +42,7 @@ module: rdf: 0 search: 0 shortcut: 0 + simple_page_manager: 0 stage_file_proxy: 0 system: 0 taxonomy: 0 diff --git a/config/sync/simple_page_manager.page.home.yml b/config/sync/simple_page_manager.page.home.yml new file mode 100644 index 000000000..4574477ad --- /dev/null +++ b/config/sync/simple_page_manager.page.home.yml @@ -0,0 +1,42 @@ +uuid: a3ad7d9b-16a0-4e75-a857-c8676836abea +langcode: en +status: true +dependencies: + module: + - custom_markup_block + - layout_discovery +id: home +label: Home +path: /home +sections: + - + layout_id: layout_onecol + layout_settings: + label: Home + components: + 39b6a30f-fc66-4c7a-878b-41f57c6884e4: + uuid: 39b6a30f-fc66-4c7a-878b-41f57c6884e4 + region: content + configuration: + id: custom_markup + label: Welcome + provider: custom_markup_block + label_display: '0' + markup: + format: basic_html + value: "

What is DemocraticLi?

\r\n\r\n

As a voter, learning everything you need to know to make an informed decision can be overwhelming. DemocraticLi helps you find out about the people and issues you're concerned with and fosters discussion around important topics with other informed users.

\r\n\r\n

Figma

\r\n\r\n

Github

\r\n\r\n

Meetup

\r\n\r\n

Discord

\r\n\r\n

Trello

\r\n\r\n

DemocracyLab

\r\n" + context_mapping: { } + additional: { } + weight: 0 + third_party_settings: { } +menu: + title: '' + description: '' + weight: 0 + enabled: false + menu_name: main + parent: '' + expanded: false +access: + type: none + options: { } diff --git a/config/sync/system.site.yml b/config/sync/system.site.yml index 37550f750..528592d60 100644 --- a/config/sync/system.site.yml +++ b/config/sync/system.site.yml @@ -1,11 +1,11 @@ uuid: d2e62de4-82f5-47c5-9f63-d9e9d7ed2d1a name: DemocraticLi -mail: admin@example.com +mail: democraticli+mail@andrewmorton.info slogan: '' page: 403: '' 404: '' - front: /node/1 + front: /home admin_compact_mode: false weight_select_max: 100 langcode: en diff --git a/config/sync/system.theme.global.yml b/config/sync/system.theme.global.yml index 9de9179d3..a72210c8d 100644 --- a/config/sync/system.theme.global.yml +++ b/config/sync/system.theme.global.yml @@ -11,6 +11,6 @@ features: logo: path: 'public://Screenshot_2021-01-09 Figma(4).png' url: '' - use_default: false + use_default: true _core: default_config_hash: 9rAU4Pku7eMBQxauQqAgjzlcicFZ2As6zEa6zvTlCB8 diff --git a/vendor/composer/InstalledVersions.php b/vendor/composer/InstalledVersions.php index 5686b98f8..6da5f5585 100644 --- a/vendor/composer/InstalledVersions.php +++ b/vendor/composer/InstalledVersions.php @@ -19,7 +19,7 @@ class InstalledVersions 'aliases' => array ( ), - 'reference' => 'fa4bea9228cf33b7ac3f112d0642c0996b875734', + 'reference' => '8f302373d26733dad19be0a40778eb1a24436f0f', 'name' => 'drupal/recommended-project', ), 'versions' => @@ -184,6 +184,51 @@ class InstalledVersions ), 'reference' => 'ce77a7ba1770462cd705a91a151b6c3746f9c6ad', ), + 'doctrine/cache' => + array ( + 'pretty_version' => '1.10.2', + 'version' => '1.10.2.0', + 'aliases' => + array ( + ), + 'reference' => '13e3381b25847283a91948d04640543941309727', + ), + 'doctrine/collections' => + array ( + 'pretty_version' => '1.6.7', + 'version' => '1.6.7.0', + 'aliases' => + array ( + ), + 'reference' => '55f8b799269a1a472457bd1a41b4f379d4cfba4a', + ), + 'doctrine/common' => + array ( + 'pretty_version' => '2.13.3', + 'version' => '2.13.3.0', + 'aliases' => + array ( + ), + 'reference' => 'f3812c026e557892c34ef37f6ab808a6b567da7f', + ), + 'doctrine/event-manager' => + array ( + 'pretty_version' => '1.1.1', + 'version' => '1.1.1.0', + 'aliases' => + array ( + ), + 'reference' => '41370af6a30faa9dc0368c4a6814d596e81aba7f', + ), + 'doctrine/inflector' => + array ( + 'pretty_version' => '1.4.3', + 'version' => '1.4.3.0', + 'aliases' => + array ( + ), + 'reference' => '4650c8b30c753a76bf44fb2ed00117d6f367490c', + ), 'doctrine/lexer' => array ( 'pretty_version' => '1.2.1', @@ -193,6 +238,15 @@ class InstalledVersions ), 'reference' => 'e864bbf5904cb8f5bb334f99209b48018522f042', ), + 'doctrine/persistence' => + array ( + 'pretty_version' => '1.3.8', + 'version' => '1.3.8.0', + 'aliases' => + array ( + ), + 'reference' => '7a6eac9fb6f61bba91328f15aa7547f4806ca288', + ), 'doctrine/reflection' => array ( 'pretty_version' => '1.2.2', @@ -313,6 +367,15 @@ class InstalledVersions 0 => '9.1.5', ), ), + 'drupal/ckeditor_div_manager' => + array ( + 'pretty_version' => '1.1.0', + 'version' => '1.1.0.0', + 'aliases' => + array ( + ), + 'reference' => '8.x-1.1', + ), 'drupal/claro' => array ( 'replaced' => @@ -651,6 +714,15 @@ class InstalledVersions 0 => '9.1.5', ), ), + 'drupal/devel' => + array ( + 'pretty_version' => '4.1.1', + 'version' => '4.1.1.0', + 'aliases' => + array ( + ), + 'reference' => '4.1.1', + ), 'drupal/dynamic_page_cache' => array ( 'replaced' => @@ -942,7 +1014,7 @@ class InstalledVersions 'aliases' => array ( ), - 'reference' => 'fa4bea9228cf33b7ac3f112d0642c0996b875734', + 'reference' => '8f302373d26733dad19be0a40778eb1a24436f0f', ), 'drupal/responsive_image' => array ( diff --git a/vendor/composer/autoload_files.php b/vendor/composer/autoload_files.php index 2f8a2c540..14ebde0fe 100644 --- a/vendor/composer/autoload_files.php +++ b/vendor/composer/autoload_files.php @@ -17,8 +17,8 @@ '7b11c4dc42b3b3023073cb14e519683c' => $vendorDir . '/ralouphie/getallheaders/src/getallheaders.php', 'c964ee0ededf28c96ebd9db5099ef910' => $vendorDir . '/guzzlehttp/promises/src/functions_include.php', 'a0edc8309cc5e1d60e3047b5df6b7052' => $vendorDir . '/guzzlehttp/psr7/src/functions_include.php', - '37a3dc5111fe8f707ab4c132ef1dbc62' => $vendorDir . '/guzzlehttp/guzzle/src/functions_include.php', '667aeda72477189d0494fecd327c3641' => $vendorDir . '/symfony/var-dumper/Resources/functions/dump.php', + '37a3dc5111fe8f707ab4c132ef1dbc62' => $vendorDir . '/guzzlehttp/guzzle/src/functions_include.php', '07d7f1a47144818725fd8d91a907ac57' => $vendorDir . '/laminas/laminas-diactoros/src/functions/create_uploaded_file.php', 'da94ac5d3ca7d2dbab84ce561ce72bfd' => $vendorDir . '/laminas/laminas-diactoros/src/functions/marshal_headers_from_sapi.php', '3d97c8dcdfba8cb85d3b34f116bb248b' => $vendorDir . '/laminas/laminas-diactoros/src/functions/marshal_method_from_sapi.php', diff --git a/vendor/composer/autoload_psr4.php b/vendor/composer/autoload_psr4.php index 193f5cf30..5c0901d9b 100644 --- a/vendor/composer/autoload_psr4.php +++ b/vendor/composer/autoload_psr4.php @@ -72,9 +72,14 @@ 'Drupal\\Composer\\Plugin\\ProjectMessage\\' => array($vendorDir . '/drupal/core-project-message'), 'Drupal\\Component\\' => array($baseDir . '/web/core/lib/Drupal/Component'), 'DrupalCodeGenerator\\' => array($vendorDir . '/chi-teck/drupal-code-generator/src'), + 'Doctrine\\Persistence\\' => array($vendorDir . '/doctrine/persistence/lib/Doctrine/Persistence'), + 'Doctrine\\Inflector\\' => array($vendorDir . '/doctrine/inflector/lib/Doctrine/Inflector'), 'Doctrine\\Common\\Lexer\\' => array($vendorDir . '/doctrine/lexer/lib/Doctrine/Common/Lexer'), + 'Doctrine\\Common\\Inflector\\' => array($vendorDir . '/doctrine/inflector/lib/Doctrine/Common/Inflector'), + 'Doctrine\\Common\\Collections\\' => array($vendorDir . '/doctrine/collections/lib/Doctrine/Common/Collections'), + 'Doctrine\\Common\\Cache\\' => array($vendorDir . '/doctrine/cache/lib/Doctrine/Common/Cache'), 'Doctrine\\Common\\Annotations\\' => array($vendorDir . '/doctrine/annotations/lib/Doctrine/Common/Annotations'), - 'Doctrine\\Common\\' => array($vendorDir . '/doctrine/reflection/lib/Doctrine/Common'), + 'Doctrine\\Common\\' => array($vendorDir . '/doctrine/reflection/lib/Doctrine/Common', $vendorDir . '/doctrine/event-manager/lib/Doctrine/Common', $vendorDir . '/doctrine/persistence/lib/Doctrine/Common', $vendorDir . '/doctrine/common/lib/Doctrine/Common'), 'Consolidation\\SiteProcess\\' => array($vendorDir . '/consolidation/site-process/src'), 'Consolidation\\SiteAlias\\' => array($vendorDir . '/consolidation/site-alias/src'), 'Consolidation\\OutputFormatters\\' => array($vendorDir . '/consolidation/output-formatters/src'), diff --git a/vendor/composer/autoload_static.php b/vendor/composer/autoload_static.php index 74ff2c298..62f194031 100644 --- a/vendor/composer/autoload_static.php +++ b/vendor/composer/autoload_static.php @@ -18,8 +18,8 @@ class ComposerStaticInitce111dca2feb0d40b3649321dbc25bad '7b11c4dc42b3b3023073cb14e519683c' => __DIR__ . '/..' . '/ralouphie/getallheaders/src/getallheaders.php', 'c964ee0ededf28c96ebd9db5099ef910' => __DIR__ . '/..' . '/guzzlehttp/promises/src/functions_include.php', 'a0edc8309cc5e1d60e3047b5df6b7052' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/functions_include.php', - '37a3dc5111fe8f707ab4c132ef1dbc62' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/functions_include.php', '667aeda72477189d0494fecd327c3641' => __DIR__ . '/..' . '/symfony/var-dumper/Resources/functions/dump.php', + '37a3dc5111fe8f707ab4c132ef1dbc62' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/functions_include.php', '07d7f1a47144818725fd8d91a907ac57' => __DIR__ . '/..' . '/laminas/laminas-diactoros/src/functions/create_uploaded_file.php', 'da94ac5d3ca7d2dbab84ce561ce72bfd' => __DIR__ . '/..' . '/laminas/laminas-diactoros/src/functions/marshal_headers_from_sapi.php', '3d97c8dcdfba8cb85d3b34f116bb248b' => __DIR__ . '/..' . '/laminas/laminas-diactoros/src/functions/marshal_method_from_sapi.php', @@ -144,7 +144,12 @@ class ComposerStaticInitce111dca2feb0d40b3649321dbc25bad 'Drupal\\Composer\\Plugin\\ProjectMessage\\' => 38, 'Drupal\\Component\\' => 17, 'DrupalCodeGenerator\\' => 20, + 'Doctrine\\Persistence\\' => 21, + 'Doctrine\\Inflector\\' => 19, 'Doctrine\\Common\\Lexer\\' => 22, + 'Doctrine\\Common\\Inflector\\' => 26, + 'Doctrine\\Common\\Collections\\' => 28, + 'Doctrine\\Common\\Cache\\' => 22, 'Doctrine\\Common\\Annotations\\' => 28, 'Doctrine\\Common\\' => 16, ), @@ -432,10 +437,30 @@ class ComposerStaticInitce111dca2feb0d40b3649321dbc25bad array ( 0 => __DIR__ . '/..' . '/chi-teck/drupal-code-generator/src', ), + 'Doctrine\\Persistence\\' => + array ( + 0 => __DIR__ . '/..' . '/doctrine/persistence/lib/Doctrine/Persistence', + ), + 'Doctrine\\Inflector\\' => + array ( + 0 => __DIR__ . '/..' . '/doctrine/inflector/lib/Doctrine/Inflector', + ), 'Doctrine\\Common\\Lexer\\' => array ( 0 => __DIR__ . '/..' . '/doctrine/lexer/lib/Doctrine/Common/Lexer', ), + 'Doctrine\\Common\\Inflector\\' => + array ( + 0 => __DIR__ . '/..' . '/doctrine/inflector/lib/Doctrine/Common/Inflector', + ), + 'Doctrine\\Common\\Collections\\' => + array ( + 0 => __DIR__ . '/..' . '/doctrine/collections/lib/Doctrine/Common/Collections', + ), + 'Doctrine\\Common\\Cache\\' => + array ( + 0 => __DIR__ . '/..' . '/doctrine/cache/lib/Doctrine/Common/Cache', + ), 'Doctrine\\Common\\Annotations\\' => array ( 0 => __DIR__ . '/..' . '/doctrine/annotations/lib/Doctrine/Common/Annotations', @@ -443,6 +468,9 @@ class ComposerStaticInitce111dca2feb0d40b3649321dbc25bad 'Doctrine\\Common\\' => array ( 0 => __DIR__ . '/..' . '/doctrine/reflection/lib/Doctrine/Common', + 1 => __DIR__ . '/..' . '/doctrine/event-manager/lib/Doctrine/Common', + 2 => __DIR__ . '/..' . '/doctrine/persistence/lib/Doctrine/Common', + 3 => __DIR__ . '/..' . '/doctrine/common/lib/Doctrine/Common', ), 'Consolidation\\SiteProcess\\' => array ( diff --git a/vendor/composer/installed.json b/vendor/composer/installed.json index bc409ba7e..f49813932 100644 --- a/vendor/composer/installed.json +++ b/vendor/composer/installed.json @@ -1191,6 +1191,481 @@ }, "install-path": "../doctrine/annotations" }, + { + "name": "doctrine/cache", + "version": "1.10.2", + "version_normalized": "1.10.2.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/cache.git", + "reference": "13e3381b25847283a91948d04640543941309727" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/cache/zipball/13e3381b25847283a91948d04640543941309727", + "reference": "13e3381b25847283a91948d04640543941309727", + "shasum": "" + }, + "require": { + "php": "~7.1 || ^8.0" + }, + "conflict": { + "doctrine/common": ">2.2,<2.4" + }, + "require-dev": { + "alcaeus/mongo-php-adapter": "^1.1", + "doctrine/coding-standard": "^6.0", + "mongodb/mongodb": "^1.1", + "phpunit/phpunit": "^7.0", + "predis/predis": "~1.0" + }, + "suggest": { + "alcaeus/mongo-php-adapter": "Required to use legacy MongoDB driver" + }, + "time": "2020-07-07T18:54:01+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.9.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Doctrine\\Common\\Cache\\": "lib/Doctrine/Common/Cache" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "PHP Doctrine Cache library is a popular cache implementation that supports many different drivers such as redis, memcache, apc, mongodb and others.", + "homepage": "https://www.doctrine-project.org/projects/cache.html", + "keywords": [ + "abstraction", + "apcu", + "cache", + "caching", + "couchdb", + "memcached", + "php", + "redis", + "xcache" + ], + "support": { + "issues": "https://github.com/doctrine/cache/issues", + "source": "https://github.com/doctrine/cache/tree/1.10.x" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fcache", + "type": "tidelift" + } + ], + "install-path": "../doctrine/cache" + }, + { + "name": "doctrine/collections", + "version": "1.6.7", + "version_normalized": "1.6.7.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/collections.git", + "reference": "55f8b799269a1a472457bd1a41b4f379d4cfba4a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/collections/zipball/55f8b799269a1a472457bd1a41b4f379d4cfba4a", + "reference": "55f8b799269a1a472457bd1a41b4f379d4cfba4a", + "shasum": "" + }, + "require": { + "php": "^7.1.3 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^6.0", + "phpstan/phpstan-shim": "^0.9.2", + "phpunit/phpunit": "^7.0", + "vimeo/psalm": "^3.8.1" + }, + "time": "2020-07-27T17:53:49+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Doctrine\\Common\\Collections\\": "lib/Doctrine/Common/Collections" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "PHP Doctrine Collections library that adds additional functionality on top of PHP arrays.", + "homepage": "https://www.doctrine-project.org/projects/collections.html", + "keywords": [ + "array", + "collections", + "iterators", + "php" + ], + "support": { + "issues": "https://github.com/doctrine/collections/issues", + "source": "https://github.com/doctrine/collections/tree/1.6.7" + }, + "install-path": "../doctrine/collections" + }, + { + "name": "doctrine/common", + "version": "2.13.3", + "version_normalized": "2.13.3.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/common.git", + "reference": "f3812c026e557892c34ef37f6ab808a6b567da7f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/common/zipball/f3812c026e557892c34ef37f6ab808a6b567da7f", + "reference": "f3812c026e557892c34ef37f6ab808a6b567da7f", + "shasum": "" + }, + "require": { + "doctrine/annotations": "^1.0", + "doctrine/cache": "^1.0", + "doctrine/collections": "^1.0", + "doctrine/event-manager": "^1.0", + "doctrine/inflector": "^1.0", + "doctrine/lexer": "^1.0", + "doctrine/persistence": "^1.3.3", + "doctrine/reflection": "^1.0", + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^1.0", + "phpstan/phpstan": "^0.11", + "phpstan/phpstan-phpunit": "^0.11", + "phpunit/phpunit": "^7.0", + "squizlabs/php_codesniffer": "^3.0", + "symfony/phpunit-bridge": "^4.0.5" + }, + "time": "2020-06-05T16:46:05+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.11.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Doctrine\\Common\\": "lib/Doctrine/Common" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + }, + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com" + } + ], + "description": "PHP Doctrine Common project is a library that provides additional functionality that other Doctrine projects depend on such as better reflection support, persistence interfaces, proxies, event system and much more.", + "homepage": "https://www.doctrine-project.org/projects/common.html", + "keywords": [ + "common", + "doctrine", + "php" + ], + "support": { + "issues": "https://github.com/doctrine/common/issues", + "source": "https://github.com/doctrine/common/tree/2.13.x" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fcommon", + "type": "tidelift" + } + ], + "install-path": "../doctrine/common" + }, + { + "name": "doctrine/event-manager", + "version": "1.1.1", + "version_normalized": "1.1.1.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/event-manager.git", + "reference": "41370af6a30faa9dc0368c4a6814d596e81aba7f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/event-manager/zipball/41370af6a30faa9dc0368c4a6814d596e81aba7f", + "reference": "41370af6a30faa9dc0368c4a6814d596e81aba7f", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "conflict": { + "doctrine/common": "<2.9@dev" + }, + "require-dev": { + "doctrine/coding-standard": "^6.0", + "phpunit/phpunit": "^7.0" + }, + "time": "2020-05-29T18:28:51+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Doctrine\\Common\\": "lib/Doctrine/Common" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + }, + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com" + } + ], + "description": "The Doctrine Event Manager is a simple PHP event system that was built to be used with the various Doctrine projects.", + "homepage": "https://www.doctrine-project.org/projects/event-manager.html", + "keywords": [ + "event", + "event dispatcher", + "event manager", + "event system", + "events" + ], + "support": { + "issues": "https://github.com/doctrine/event-manager/issues", + "source": "https://github.com/doctrine/event-manager/tree/1.1.x" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fevent-manager", + "type": "tidelift" + } + ], + "install-path": "../doctrine/event-manager" + }, + { + "name": "doctrine/inflector", + "version": "1.4.3", + "version_normalized": "1.4.3.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/inflector.git", + "reference": "4650c8b30c753a76bf44fb2ed00117d6f367490c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/inflector/zipball/4650c8b30c753a76bf44fb2ed00117d6f367490c", + "reference": "4650c8b30c753a76bf44fb2ed00117d6f367490c", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^7.0", + "phpstan/phpstan": "^0.11", + "phpstan/phpstan-phpunit": "^0.11", + "phpstan/phpstan-strict-rules": "^0.11", + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0" + }, + "time": "2020-05-29T07:19:59+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Doctrine\\Common\\Inflector\\": "lib/Doctrine/Common/Inflector", + "Doctrine\\Inflector\\": "lib/Doctrine/Inflector" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "PHP Doctrine Inflector is a small library that can perform string manipulations with regard to upper/lowercase and singular/plural forms of words.", + "homepage": "https://www.doctrine-project.org/projects/inflector.html", + "keywords": [ + "inflection", + "inflector", + "lowercase", + "manipulation", + "php", + "plural", + "singular", + "strings", + "uppercase", + "words" + ], + "support": { + "issues": "https://github.com/doctrine/inflector/issues", + "source": "https://github.com/doctrine/inflector/tree/1.4.x" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finflector", + "type": "tidelift" + } + ], + "install-path": "../doctrine/inflector" + }, { "name": "doctrine/lexer", "version": "1.2.1", @@ -1274,6 +1749,111 @@ ], "install-path": "../doctrine/lexer" }, + { + "name": "doctrine/persistence", + "version": "1.3.8", + "version_normalized": "1.3.8.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/persistence.git", + "reference": "7a6eac9fb6f61bba91328f15aa7547f4806ca288" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/persistence/zipball/7a6eac9fb6f61bba91328f15aa7547f4806ca288", + "reference": "7a6eac9fb6f61bba91328f15aa7547f4806ca288", + "shasum": "" + }, + "require": { + "doctrine/annotations": "^1.0", + "doctrine/cache": "^1.0", + "doctrine/collections": "^1.0", + "doctrine/event-manager": "^1.0", + "doctrine/reflection": "^1.2", + "php": "^7.1 || ^8.0" + }, + "conflict": { + "doctrine/common": "<2.10@dev" + }, + "require-dev": { + "doctrine/coding-standard": "^6.0", + "phpstan/phpstan": "^0.11", + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0", + "vimeo/psalm": "^3.11" + }, + "time": "2020-06-20T12:56:16+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Doctrine\\Common\\": "lib/Doctrine/Common", + "Doctrine\\Persistence\\": "lib/Doctrine/Persistence" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + }, + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com" + } + ], + "description": "The Doctrine Persistence project is a set of shared interfaces and functionality that the different Doctrine object mappers share.", + "homepage": "https://doctrine-project.org/projects/persistence.html", + "keywords": [ + "mapper", + "object", + "odm", + "orm", + "persistence" + ], + "support": { + "issues": "https://github.com/doctrine/persistence/issues", + "source": "https://github.com/doctrine/persistence/tree/1.3.x" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fpersistence", + "type": "tidelift" + } + ], + "install-path": "../doctrine/persistence" + }, { "name": "doctrine/reflection", "version": "1.2.2", @@ -1563,6 +2143,59 @@ }, "install-path": "../../web/themes/contrib/bootstrap_barrio" }, + { + "name": "drupal/ckeditor_div_manager", + "version": "1.1.0", + "version_normalized": "1.1.0.0", + "source": { + "type": "git", + "url": "https://git.drupalcode.org/project/ckeditor_div_manager.git", + "reference": "8.x-1.1" + }, + "dist": { + "type": "zip", + "url": "https://ftp.drupal.org/files/projects/ckeditor_div_manager-8.x-1.1.zip", + "reference": "8.x-1.1", + "shasum": "2aba67e132e0d54886e8bdc9914f51a6160d3875" + }, + "require": { + "drupal/core": "^8 || ^9" + }, + "type": "drupal-module", + "extra": { + "drupal": { + "version": "8.x-1.1", + "datestamp": "1615574655", + "security-coverage": { + "status": "covered", + "message": "Covered by Drupal's security advisory policy" + } + } + }, + "installation-source": "dist", + "notification-url": "https://packages.drupal.org/8/downloads", + "license": [ + "GPL-2.0+" + ], + "authors": [ + { + "name": "Corey Eiseman", + "homepage": "https://www.drupal.org/u/toegristle", + "role": "Maintainer" + }, + { + "name": "toegristle", + "homepage": "https://www.drupal.org/user/1802910" + } + ], + "description": "Adds the Div Container Manager plugin to CKEditor.", + "homepage": "https://www.drupal.org/project/ckeditor_div_manager", + "support": { + "source": "https://cgit.drupalcode.org/ckeditor_div_manager", + "issues": "https://www.drupal.org/project/issues/ckeditor_div_manager" + }, + "install-path": "../../web/modules/contrib/ckeditor_div_manager" + }, { "name": "drupal/config_filter", "version": "2.2.0", @@ -2279,6 +2912,75 @@ }, "install-path": "../../web/modules/contrib/custom_markup_block" }, + { + "name": "drupal/devel", + "version": "4.1.1", + "version_normalized": "4.1.1.0", + "source": { + "type": "git", + "url": "https://git.drupalcode.org/project/devel.git", + "reference": "4.1.1" + }, + "dist": { + "type": "zip", + "url": "https://ftp.drupal.org/files/projects/devel-4.1.1.zip", + "reference": "4.1.1", + "shasum": "88e5d49dda26a3136291ecd97bc6c8e897b24198" + }, + "require": { + "doctrine/common": "^2.7", + "drupal/core": "^8.8 || ^9", + "symfony/var-dumper": "^4 || ^5" + }, + "conflict": { + "kint-php/kint": "<3" + }, + "require-dev": { + "drush/drush": "^10" + }, + "suggest": { + "kint-php/kint": "Kint provides an informative display of arrays/objects. Useful for debugging and developing." + }, + "type": "drupal-module", + "extra": { + "drupal": { + "version": "4.1.1", + "datestamp": "1609419527", + "security-coverage": { + "status": "covered", + "message": "Covered by Drupal's security advisory policy" + } + }, + "drush": { + "services": { + "drush.services.yml": "^9 || ^10" + } + } + }, + "installation-source": "dist", + "notification-url": "https://packages.drupal.org/8/downloads", + "license": [ + "GPL-2.0-or-later" + ], + "authors": [ + { + "name": "drupalspoons", + "homepage": "https://www.drupal.org/user/3647684" + }, + { + "name": "moshe weitzman", + "homepage": "https://www.drupal.org/user/23" + } + ], + "description": "Various blocks, pages, and functions for developers.", + "homepage": "https://www.drupal.org/project/devel", + "support": { + "source": "https://gitlab.com/drupalspoons/devel", + "issues": "https://gitlab.com/drupalspoons/devel/-/issues", + "slack": "https://drupal.slack.com/archives/C012WAW1MH6" + }, + "install-path": "../../web/modules/contrib/devel" + }, { "name": "drupal/fences", "version": "2.0.0-rc1", diff --git a/vendor/composer/installed.php b/vendor/composer/installed.php index 736905c8a..2948ea54f 100644 --- a/vendor/composer/installed.php +++ b/vendor/composer/installed.php @@ -6,7 +6,7 @@ 'aliases' => array ( ), - 'reference' => 'fa4bea9228cf33b7ac3f112d0642c0996b875734', + 'reference' => '8f302373d26733dad19be0a40778eb1a24436f0f', 'name' => 'drupal/recommended-project', ), 'versions' => @@ -171,6 +171,51 @@ ), 'reference' => 'ce77a7ba1770462cd705a91a151b6c3746f9c6ad', ), + 'doctrine/cache' => + array ( + 'pretty_version' => '1.10.2', + 'version' => '1.10.2.0', + 'aliases' => + array ( + ), + 'reference' => '13e3381b25847283a91948d04640543941309727', + ), + 'doctrine/collections' => + array ( + 'pretty_version' => '1.6.7', + 'version' => '1.6.7.0', + 'aliases' => + array ( + ), + 'reference' => '55f8b799269a1a472457bd1a41b4f379d4cfba4a', + ), + 'doctrine/common' => + array ( + 'pretty_version' => '2.13.3', + 'version' => '2.13.3.0', + 'aliases' => + array ( + ), + 'reference' => 'f3812c026e557892c34ef37f6ab808a6b567da7f', + ), + 'doctrine/event-manager' => + array ( + 'pretty_version' => '1.1.1', + 'version' => '1.1.1.0', + 'aliases' => + array ( + ), + 'reference' => '41370af6a30faa9dc0368c4a6814d596e81aba7f', + ), + 'doctrine/inflector' => + array ( + 'pretty_version' => '1.4.3', + 'version' => '1.4.3.0', + 'aliases' => + array ( + ), + 'reference' => '4650c8b30c753a76bf44fb2ed00117d6f367490c', + ), 'doctrine/lexer' => array ( 'pretty_version' => '1.2.1', @@ -180,6 +225,15 @@ ), 'reference' => 'e864bbf5904cb8f5bb334f99209b48018522f042', ), + 'doctrine/persistence' => + array ( + 'pretty_version' => '1.3.8', + 'version' => '1.3.8.0', + 'aliases' => + array ( + ), + 'reference' => '7a6eac9fb6f61bba91328f15aa7547f4806ca288', + ), 'doctrine/reflection' => array ( 'pretty_version' => '1.2.2', @@ -300,6 +354,15 @@ 0 => '9.1.5', ), ), + 'drupal/ckeditor_div_manager' => + array ( + 'pretty_version' => '1.1.0', + 'version' => '1.1.0.0', + 'aliases' => + array ( + ), + 'reference' => '8.x-1.1', + ), 'drupal/claro' => array ( 'replaced' => @@ -638,6 +701,15 @@ 0 => '9.1.5', ), ), + 'drupal/devel' => + array ( + 'pretty_version' => '4.1.1', + 'version' => '4.1.1.0', + 'aliases' => + array ( + ), + 'reference' => '4.1.1', + ), 'drupal/dynamic_page_cache' => array ( 'replaced' => @@ -929,7 +1001,7 @@ 'aliases' => array ( ), - 'reference' => 'fa4bea9228cf33b7ac3f112d0642c0996b875734', + 'reference' => '8f302373d26733dad19be0a40778eb1a24436f0f', ), 'drupal/responsive_image' => array ( diff --git a/vendor/doctrine/cache/LICENSE b/vendor/doctrine/cache/LICENSE new file mode 100644 index 000000000..8c38cc1bc --- /dev/null +++ b/vendor/doctrine/cache/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2006-2015 Doctrine Project + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/doctrine/cache/README.md b/vendor/doctrine/cache/README.md new file mode 100644 index 000000000..c795a0584 --- /dev/null +++ b/vendor/doctrine/cache/README.md @@ -0,0 +1,9 @@ +# Doctrine Cache + +[![Build Status](https://img.shields.io/travis/doctrine/cache/master.svg?style=flat-square)](http://travis-ci.org/doctrine/cache) +[![Code Coverage](https://codecov.io/gh/doctrine/dbal/branch/cache/graph/badge.svg)](https://codecov.io/gh/doctrine/dbal/branch/master) + +[![Latest Stable Version](https://img.shields.io/packagist/v/doctrine/cache.svg?style=flat-square)](https://packagist.org/packages/doctrine/cache) +[![Total Downloads](https://img.shields.io/packagist/dt/doctrine/cache.svg?style=flat-square)](https://packagist.org/packages/doctrine/cache) + +Cache component extracted from the Doctrine Common project. [Documentation](https://www.doctrine-project.org/projects/doctrine-cache/en/current/index.html) diff --git a/vendor/doctrine/cache/UPGRADE.md b/vendor/doctrine/cache/UPGRADE.md new file mode 100644 index 000000000..e1f8a503e --- /dev/null +++ b/vendor/doctrine/cache/UPGRADE.md @@ -0,0 +1,16 @@ +# Upgrade to 1.4 + +## Minor BC Break: `Doctrine\Common\Cache\FileCache#$extension` is now `private`. + +If you need to override the value of `Doctrine\Common\Cache\FileCache#$extension`, then use the +second parameter of `Doctrine\Common\Cache\FileCache#__construct()` instead of overriding +the property in your own implementation. + +## Minor BC Break: file based caches paths changed + +`Doctrine\Common\Cache\FileCache`, `Doctrine\Common\Cache\PhpFileCache` and +`Doctrine\Common\Cache\FilesystemCache` are using a different cache paths structure. + +If you rely on warmed up caches for deployments, consider that caches generated +with `doctrine/cache` `<1.4` are not compatible with the new directory structure, +and will be ignored. diff --git a/vendor/doctrine/cache/composer.json b/vendor/doctrine/cache/composer.json new file mode 100644 index 000000000..b889aa37d --- /dev/null +++ b/vendor/doctrine/cache/composer.json @@ -0,0 +1,52 @@ +{ + "name": "doctrine/cache", + "type": "library", + "description": "PHP Doctrine Cache library is a popular cache implementation that supports many different drivers such as redis, memcache, apc, mongodb and others.", + "keywords": [ + "php", + "cache", + "caching", + "abstraction", + "redis", + "memcached", + "couchdb", + "xcache", + "apcu" + ], + "homepage": "https://www.doctrine-project.org/projects/cache.html", + "license": "MIT", + "authors": [ + {"name": "Guilherme Blanco", "email": "guilhermeblanco@gmail.com"}, + {"name": "Roman Borschel", "email": "roman@code-factory.org"}, + {"name": "Benjamin Eberlei", "email": "kontakt@beberlei.de"}, + {"name": "Jonathan Wage", "email": "jonwage@gmail.com"}, + {"name": "Johannes Schmitt", "email": "schmittjoh@gmail.com"} + ], + "require": { + "php": "~7.1 || ^8.0" + }, + "require-dev": { + "alcaeus/mongo-php-adapter": "^1.1", + "mongodb/mongodb": "^1.1", + "phpunit/phpunit": "^7.0", + "predis/predis": "~1.0", + "doctrine/coding-standard": "^6.0" + }, + "suggest": { + "alcaeus/mongo-php-adapter": "Required to use legacy MongoDB driver" + }, + "conflict": { + "doctrine/common": ">2.2,<2.4" + }, + "autoload": { + "psr-4": { "Doctrine\\Common\\Cache\\": "lib/Doctrine/Common/Cache" } + }, + "autoload-dev": { + "psr-4": { "Doctrine\\Tests\\": "tests/Doctrine/Tests" } + }, + "extra": { + "branch-alias": { + "dev-master": "1.9.x-dev" + } + } +} diff --git a/vendor/doctrine/cache/lib/Doctrine/Common/Cache/ApcCache.php b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/ApcCache.php new file mode 100644 index 000000000..138d49a53 --- /dev/null +++ b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/ApcCache.php @@ -0,0 +1,105 @@ += 50500) { + $info['num_hits'] = $info['num_hits'] ?? $info['nhits']; + $info['num_misses'] = $info['num_misses'] ?? $info['nmisses']; + $info['start_time'] = $info['start_time'] ?? $info['stime']; + } + + return [ + Cache::STATS_HITS => $info['num_hits'], + Cache::STATS_MISSES => $info['num_misses'], + Cache::STATS_UPTIME => $info['start_time'], + Cache::STATS_MEMORY_USAGE => $info['mem_size'], + Cache::STATS_MEMORY_AVAILABLE => $sma['avail_mem'], + ]; + } +} diff --git a/vendor/doctrine/cache/lib/Doctrine/Common/Cache/ApcuCache.php b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/ApcuCache.php new file mode 100644 index 000000000..a72521361 --- /dev/null +++ b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/ApcuCache.php @@ -0,0 +1,106 @@ + $info['num_hits'], + Cache::STATS_MISSES => $info['num_misses'], + Cache::STATS_UPTIME => $info['start_time'], + Cache::STATS_MEMORY_USAGE => $info['mem_size'], + Cache::STATS_MEMORY_AVAILABLE => $sma['avail_mem'], + ]; + } +} diff --git a/vendor/doctrine/cache/lib/Doctrine/Common/Cache/ArrayCache.php b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/ArrayCache.php new file mode 100644 index 000000000..1beb7098f --- /dev/null +++ b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/ArrayCache.php @@ -0,0 +1,113 @@ +upTime = time(); + } + + /** + * {@inheritdoc} + */ + protected function doFetch($id) + { + if (! $this->doContains($id)) { + $this->missesCount += 1; + + return false; + } + + $this->hitsCount += 1; + + return $this->data[$id][0]; + } + + /** + * {@inheritdoc} + */ + protected function doContains($id) + { + if (! isset($this->data[$id])) { + return false; + } + + $expiration = $this->data[$id][1]; + + if ($expiration && $expiration < time()) { + $this->doDelete($id); + + return false; + } + + return true; + } + + /** + * {@inheritdoc} + */ + protected function doSave($id, $data, $lifeTime = 0) + { + $this->data[$id] = [$data, $lifeTime ? time() + $lifeTime : false]; + + return true; + } + + /** + * {@inheritdoc} + */ + protected function doDelete($id) + { + unset($this->data[$id]); + + return true; + } + + /** + * {@inheritdoc} + */ + protected function doFlush() + { + $this->data = []; + + return true; + } + + /** + * {@inheritdoc} + */ + protected function doGetStats() + { + return [ + Cache::STATS_HITS => $this->hitsCount, + Cache::STATS_MISSES => $this->missesCount, + Cache::STATS_UPTIME => $this->upTime, + Cache::STATS_MEMORY_USAGE => null, + Cache::STATS_MEMORY_AVAILABLE => null, + ]; + } +} diff --git a/vendor/doctrine/cache/lib/Doctrine/Common/Cache/Cache.php b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/Cache.php new file mode 100644 index 000000000..456974427 --- /dev/null +++ b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/Cache.php @@ -0,0 +1,90 @@ +hits + * Number of keys that have been requested and found present. + * + * - misses + * Number of items that have been requested and not found. + * + * - uptime + * Time that the server is running. + * + * - memory_usage + * Memory used by this server to store items. + * + * - memory_available + * Memory allowed to use for storage. + * + * @return array|null An associative array with server's statistics if available, NULL otherwise. + */ + public function getStats(); +} diff --git a/vendor/doctrine/cache/lib/Doctrine/Common/Cache/CacheProvider.php b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/CacheProvider.php new file mode 100644 index 000000000..43d414f3c --- /dev/null +++ b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/CacheProvider.php @@ -0,0 +1,325 @@ +namespace = (string) $namespace; + $this->namespaceVersion = null; + } + + /** + * Retrieves the namespace that prefixes all cache ids. + * + * @return string + */ + public function getNamespace() + { + return $this->namespace; + } + + /** + * {@inheritdoc} + */ + public function fetch($id) + { + return $this->doFetch($this->getNamespacedId($id)); + } + + /** + * {@inheritdoc} + */ + public function fetchMultiple(array $keys) + { + if (empty($keys)) { + return []; + } + + // note: the array_combine() is in place to keep an association between our $keys and the $namespacedKeys + $namespacedKeys = array_combine($keys, array_map([$this, 'getNamespacedId'], $keys)); + $items = $this->doFetchMultiple($namespacedKeys); + $foundItems = []; + + // no internal array function supports this sort of mapping: needs to be iterative + // this filters and combines keys in one pass + foreach ($namespacedKeys as $requestedKey => $namespacedKey) { + if (! isset($items[$namespacedKey]) && ! array_key_exists($namespacedKey, $items)) { + continue; + } + + $foundItems[$requestedKey] = $items[$namespacedKey]; + } + + return $foundItems; + } + + /** + * {@inheritdoc} + */ + public function saveMultiple(array $keysAndValues, $lifetime = 0) + { + $namespacedKeysAndValues = []; + foreach ($keysAndValues as $key => $value) { + $namespacedKeysAndValues[$this->getNamespacedId($key)] = $value; + } + + return $this->doSaveMultiple($namespacedKeysAndValues, $lifetime); + } + + /** + * {@inheritdoc} + */ + public function contains($id) + { + return $this->doContains($this->getNamespacedId($id)); + } + + /** + * {@inheritdoc} + */ + public function save($id, $data, $lifeTime = 0) + { + return $this->doSave($this->getNamespacedId($id), $data, $lifeTime); + } + + /** + * {@inheritdoc} + */ + public function deleteMultiple(array $keys) + { + return $this->doDeleteMultiple(array_map([$this, 'getNamespacedId'], $keys)); + } + + /** + * {@inheritdoc} + */ + public function delete($id) + { + return $this->doDelete($this->getNamespacedId($id)); + } + + /** + * {@inheritdoc} + */ + public function getStats() + { + return $this->doGetStats(); + } + + /** + * {@inheritDoc} + */ + public function flushAll() + { + return $this->doFlush(); + } + + /** + * {@inheritDoc} + */ + public function deleteAll() + { + $namespaceCacheKey = $this->getNamespaceCacheKey(); + $namespaceVersion = $this->getNamespaceVersion() + 1; + + if ($this->doSave($namespaceCacheKey, $namespaceVersion)) { + $this->namespaceVersion = $namespaceVersion; + + return true; + } + + return false; + } + + /** + * Prefixes the passed id with the configured namespace value. + * + * @param string $id The id to namespace. + * + * @return string The namespaced id. + */ + private function getNamespacedId(string $id) : string + { + $namespaceVersion = $this->getNamespaceVersion(); + + return sprintf('%s[%s][%s]', $this->namespace, $id, $namespaceVersion); + } + + /** + * Returns the namespace cache key. + */ + private function getNamespaceCacheKey() : string + { + return sprintf(self::DOCTRINE_NAMESPACE_CACHEKEY, $this->namespace); + } + + /** + * Returns the namespace version. + */ + private function getNamespaceVersion() : int + { + if ($this->namespaceVersion !== null) { + return $this->namespaceVersion; + } + + $namespaceCacheKey = $this->getNamespaceCacheKey(); + $this->namespaceVersion = (int) $this->doFetch($namespaceCacheKey) ?: 1; + + return $this->namespaceVersion; + } + + /** + * Default implementation of doFetchMultiple. Each driver that supports multi-get should owerwrite it. + * + * @param array $keys Array of keys to retrieve from cache + * + * @return array Array of values retrieved for the given keys. + */ + protected function doFetchMultiple(array $keys) + { + $returnValues = []; + + foreach ($keys as $key) { + $item = $this->doFetch($key); + if ($item === false && ! $this->doContains($key)) { + continue; + } + + $returnValues[$key] = $item; + } + + return $returnValues; + } + + /** + * Fetches an entry from the cache. + * + * @param string $id The id of the cache entry to fetch. + * + * @return mixed|false The cached data or FALSE, if no cache entry exists for the given id. + */ + abstract protected function doFetch($id); + + /** + * Tests if an entry exists in the cache. + * + * @param string $id The cache id of the entry to check for. + * + * @return bool TRUE if a cache entry exists for the given cache id, FALSE otherwise. + */ + abstract protected function doContains($id); + + /** + * Default implementation of doSaveMultiple. Each driver that supports multi-put should override it. + * + * @param array $keysAndValues Array of keys and values to save in cache + * @param int $lifetime The lifetime. If != 0, sets a specific lifetime for these + * cache entries (0 => infinite lifeTime). + * + * @return bool TRUE if the operation was successful, FALSE if it wasn't. + */ + protected function doSaveMultiple(array $keysAndValues, $lifetime = 0) + { + $success = true; + + foreach ($keysAndValues as $key => $value) { + if ($this->doSave($key, $value, $lifetime)) { + continue; + } + + $success = false; + } + + return $success; + } + + /** + * Puts data into the cache. + * + * @param string $id The cache id. + * @param string $data The cache entry/data. + * @param int $lifeTime The lifetime. If != 0, sets a specific lifetime for this + * cache entry (0 => infinite lifeTime). + * + * @return bool TRUE if the entry was successfully stored in the cache, FALSE otherwise. + */ + abstract protected function doSave($id, $data, $lifeTime = 0); + + /** + * Default implementation of doDeleteMultiple. Each driver that supports multi-delete should override it. + * + * @param array $keys Array of keys to delete from cache + * + * @return bool TRUE if the operation was successful, FALSE if it wasn't + */ + protected function doDeleteMultiple(array $keys) + { + $success = true; + + foreach ($keys as $key) { + if ($this->doDelete($key)) { + continue; + } + + $success = false; + } + + return $success; + } + + /** + * Deletes a cache entry. + * + * @param string $id The cache id. + * + * @return bool TRUE if the cache entry was successfully deleted, FALSE otherwise. + */ + abstract protected function doDelete($id); + + /** + * Flushes all cache entries. + * + * @return bool TRUE if the cache entries were successfully flushed, FALSE otherwise. + */ + abstract protected function doFlush(); + + /** + * Retrieves cached information from the data store. + * + * @return array|null An associative array with server's statistics if available, NULL otherwise. + */ + abstract protected function doGetStats(); +} diff --git a/vendor/doctrine/cache/lib/Doctrine/Common/Cache/ChainCache.php b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/ChainCache.php new file mode 100644 index 000000000..8f85845c7 --- /dev/null +++ b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/ChainCache.php @@ -0,0 +1,195 @@ +cacheProviders = $cacheProviders instanceof Traversable + ? iterator_to_array($cacheProviders, false) + : array_values($cacheProviders); + } + + public function setDefaultLifeTimeForDownstreamCacheProviders(int $defaultLifeTimeForDownstreamCacheProviders) : void + { + $this->defaultLifeTimeForDownstreamCacheProviders = $defaultLifeTimeForDownstreamCacheProviders; + } + + /** + * {@inheritDoc} + */ + public function setNamespace($namespace) + { + parent::setNamespace($namespace); + + foreach ($this->cacheProviders as $cacheProvider) { + $cacheProvider->setNamespace($namespace); + } + } + + /** + * {@inheritDoc} + */ + protected function doFetch($id) + { + foreach ($this->cacheProviders as $key => $cacheProvider) { + if ($cacheProvider->doContains($id)) { + $value = $cacheProvider->doFetch($id); + + // We populate all the previous cache layers (that are assumed to be faster) + for ($subKey = $key - 1; $subKey >= 0; $subKey--) { + $this->cacheProviders[$subKey]->doSave($id, $value, $this->defaultLifeTimeForDownstreamCacheProviders); + } + + return $value; + } + } + + return false; + } + + /** + * {@inheritdoc} + */ + protected function doFetchMultiple(array $keys) + { + /** @var CacheProvider[] $traversedProviders */ + $traversedProviders = []; + $keysCount = count($keys); + $fetchedValues = []; + + foreach ($this->cacheProviders as $key => $cacheProvider) { + $fetchedValues = $cacheProvider->doFetchMultiple($keys); + + // We populate all the previous cache layers (that are assumed to be faster) + if (count($fetchedValues) === $keysCount) { + foreach ($traversedProviders as $previousCacheProvider) { + $previousCacheProvider->doSaveMultiple($fetchedValues, $this->defaultLifeTimeForDownstreamCacheProviders); + } + + return $fetchedValues; + } + + $traversedProviders[] = $cacheProvider; + } + + return $fetchedValues; + } + + /** + * {@inheritDoc} + */ + protected function doContains($id) + { + foreach ($this->cacheProviders as $cacheProvider) { + if ($cacheProvider->doContains($id)) { + return true; + } + } + + return false; + } + + /** + * {@inheritDoc} + */ + protected function doSave($id, $data, $lifeTime = 0) + { + $stored = true; + + foreach ($this->cacheProviders as $cacheProvider) { + $stored = $cacheProvider->doSave($id, $data, $lifeTime) && $stored; + } + + return $stored; + } + + /** + * {@inheritdoc} + */ + protected function doSaveMultiple(array $keysAndValues, $lifetime = 0) + { + $stored = true; + + foreach ($this->cacheProviders as $cacheProvider) { + $stored = $cacheProvider->doSaveMultiple($keysAndValues, $lifetime) && $stored; + } + + return $stored; + } + + /** + * {@inheritDoc} + */ + protected function doDelete($id) + { + $deleted = true; + + foreach ($this->cacheProviders as $cacheProvider) { + $deleted = $cacheProvider->doDelete($id) && $deleted; + } + + return $deleted; + } + + /** + * {@inheritdoc} + */ + protected function doDeleteMultiple(array $keys) + { + $deleted = true; + + foreach ($this->cacheProviders as $cacheProvider) { + $deleted = $cacheProvider->doDeleteMultiple($keys) && $deleted; + } + + return $deleted; + } + + /** + * {@inheritDoc} + */ + protected function doFlush() + { + $flushed = true; + + foreach ($this->cacheProviders as $cacheProvider) { + $flushed = $cacheProvider->doFlush() && $flushed; + } + + return $flushed; + } + + /** + * {@inheritDoc} + */ + protected function doGetStats() + { + // We return all the stats from all adapters + $stats = []; + + foreach ($this->cacheProviders as $cacheProvider) { + $stats[] = $cacheProvider->doGetStats(); + } + + return $stats; + } +} diff --git a/vendor/doctrine/cache/lib/Doctrine/Common/Cache/ClearableCache.php b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/ClearableCache.php new file mode 100644 index 000000000..b94618e46 --- /dev/null +++ b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/ClearableCache.php @@ -0,0 +1,21 @@ +bucket = $bucket; + } + + /** + * {@inheritdoc} + */ + protected function doFetch($id) + { + $id = $this->normalizeKey($id); + + try { + $document = $this->bucket->get($id); + } catch (Exception $e) { + return false; + } + + if ($document instanceof Document && $document->value !== false) { + return unserialize($document->value); + } + + return false; + } + + /** + * {@inheritdoc} + */ + protected function doContains($id) + { + $id = $this->normalizeKey($id); + + try { + $document = $this->bucket->get($id); + } catch (Exception $e) { + return false; + } + + if ($document instanceof Document) { + return ! $document->error; + } + + return false; + } + + /** + * {@inheritdoc} + */ + protected function doSave($id, $data, $lifeTime = 0) + { + $id = $this->normalizeKey($id); + + $lifeTime = $this->normalizeExpiry($lifeTime); + + try { + $encoded = serialize($data); + + $document = $this->bucket->upsert($id, $encoded, [ + 'expiry' => (int) $lifeTime, + ]); + } catch (Exception $e) { + return false; + } + + if ($document instanceof Document) { + return ! $document->error; + } + + return false; + } + + /** + * {@inheritdoc} + */ + protected function doDelete($id) + { + $id = $this->normalizeKey($id); + + try { + $document = $this->bucket->remove($id); + } catch (Exception $e) { + return $e->getCode() === self::KEY_NOT_FOUND; + } + + if ($document instanceof Document) { + return ! $document->error; + } + + return false; + } + + /** + * {@inheritdoc} + */ + protected function doFlush() + { + $manager = $this->bucket->manager(); + + // Flush does not return with success or failure, and must be enabled per bucket on the server. + // Store a marker item so that we will know if it was successful. + $this->doSave(__METHOD__, true, 60); + + $manager->flush(); + + if ($this->doContains(__METHOD__)) { + $this->doDelete(__METHOD__); + + return false; + } + + return true; + } + + /** + * {@inheritdoc} + */ + protected function doGetStats() + { + $manager = $this->bucket->manager(); + $stats = $manager->info(); + $nodes = $stats['nodes']; + $node = $nodes[0]; + $interestingStats = $node['interestingStats']; + + return [ + Cache::STATS_HITS => $interestingStats['get_hits'], + Cache::STATS_MISSES => $interestingStats['cmd_get'] - $interestingStats['get_hits'], + Cache::STATS_UPTIME => $node['uptime'], + Cache::STATS_MEMORY_USAGE => $interestingStats['mem_used'], + Cache::STATS_MEMORY_AVAILABLE => $node['memoryFree'], + ]; + } + + private function normalizeKey(string $id) : string + { + $normalized = substr($id, 0, self::MAX_KEY_LENGTH); + + if ($normalized === false) { + return $id; + } + + return $normalized; + } + + /** + * Expiry treated as a unix timestamp instead of an offset if expiry is greater than 30 days. + * + * @src https://developer.couchbase.com/documentation/server/4.1/developer-guide/expiry.html + */ + private function normalizeExpiry(int $expiry) : int + { + if ($expiry > self::THIRTY_DAYS_IN_SECONDS) { + return time() + $expiry; + } + + return $expiry; + } +} diff --git a/vendor/doctrine/cache/lib/Doctrine/Common/Cache/CouchbaseCache.php b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/CouchbaseCache.php new file mode 100644 index 000000000..2c62c2ea8 --- /dev/null +++ b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/CouchbaseCache.php @@ -0,0 +1,105 @@ +couchbase = $couchbase; + } + + /** + * Gets the Couchbase instance used by the cache. + * + * @return Couchbase|null + */ + public function getCouchbase() + { + return $this->couchbase; + } + + /** + * {@inheritdoc} + */ + protected function doFetch($id) + { + return $this->couchbase->get($id) ?: false; + } + + /** + * {@inheritdoc} + */ + protected function doContains($id) + { + return $this->couchbase->get($id) !== null; + } + + /** + * {@inheritdoc} + */ + protected function doSave($id, $data, $lifeTime = 0) + { + if ($lifeTime > 30 * 24 * 3600) { + $lifeTime = time() + $lifeTime; + } + + return $this->couchbase->set($id, $data, (int) $lifeTime); + } + + /** + * {@inheritdoc} + */ + protected function doDelete($id) + { + return $this->couchbase->delete($id); + } + + /** + * {@inheritdoc} + */ + protected function doFlush() + { + return $this->couchbase->flush(); + } + + /** + * {@inheritdoc} + */ + protected function doGetStats() + { + $stats = $this->couchbase->getStats(); + $servers = $this->couchbase->getServers(); + $server = explode(':', $servers[0]); + $key = $server[0] . ':11210'; + $stats = $stats[$key]; + + return [ + Cache::STATS_HITS => $stats['get_hits'], + Cache::STATS_MISSES => $stats['get_misses'], + Cache::STATS_UPTIME => $stats['uptime'], + Cache::STATS_MEMORY_USAGE => $stats['bytes'], + Cache::STATS_MEMORY_AVAILABLE => $stats['limit_maxbytes'], + ]; + } +} diff --git a/vendor/doctrine/cache/lib/Doctrine/Common/Cache/ExtMongoDBCache.php b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/ExtMongoDBCache.php new file mode 100644 index 000000000..b06f86232 --- /dev/null +++ b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/ExtMongoDBCache.php @@ -0,0 +1,198 @@ +collection = $collection->withOptions(['typeMap' => null]); + $this->database = new Database($collection->getManager(), $collection->getDatabaseName()); + } + + /** + * {@inheritdoc} + */ + protected function doFetch($id) + { + $document = $this->collection->findOne(['_id' => $id], [MongoDBCache::DATA_FIELD, MongoDBCache::EXPIRATION_FIELD]); + + if ($document === null) { + return false; + } + + if ($this->isExpired($document)) { + $this->createExpirationIndex(); + $this->doDelete($id); + + return false; + } + + return unserialize($document[MongoDBCache::DATA_FIELD]->getData()); + } + + /** + * {@inheritdoc} + */ + protected function doContains($id) + { + $document = $this->collection->findOne(['_id' => $id], [MongoDBCache::EXPIRATION_FIELD]); + + if ($document === null) { + return false; + } + + if ($this->isExpired($document)) { + $this->createExpirationIndex(); + $this->doDelete($id); + + return false; + } + + return true; + } + + /** + * {@inheritdoc} + */ + protected function doSave($id, $data, $lifeTime = 0) + { + try { + $this->collection->updateOne( + ['_id' => $id], + [ + '$set' => [ + MongoDBCache::EXPIRATION_FIELD => ($lifeTime > 0 ? new UTCDateTime((time() + $lifeTime) * 1000): null), + MongoDBCache::DATA_FIELD => new Binary(serialize($data), Binary::TYPE_GENERIC), + ], + ], + ['upsert' => true] + ); + } catch (Exception $e) { + return false; + } + + return true; + } + + /** + * {@inheritdoc} + */ + protected function doDelete($id) + { + try { + $this->collection->deleteOne(['_id' => $id]); + } catch (Exception $e) { + return false; + } + + return true; + } + + /** + * {@inheritdoc} + */ + protected function doFlush() + { + try { + // Use remove() in lieu of drop() to maintain any collection indexes + $this->collection->deleteMany([]); + } catch (Exception $e) { + return false; + } + + return true; + } + + /** + * {@inheritdoc} + */ + protected function doGetStats() + { + $uptime = null; + $memoryUsage = null; + + try { + $serverStatus = $this->database->command([ + 'serverStatus' => 1, + 'locks' => 0, + 'metrics' => 0, + 'recordStats' => 0, + 'repl' => 0, + ])->toArray()[0]; + $uptime = $serverStatus['uptime'] ?? null; + } catch (Exception $e) { + } + + try { + $collStats = $this->database->command(['collStats' => $this->collection->getCollectionName()])->toArray()[0]; + $memoryUsage = $collStats['size'] ?? null; + } catch (Exception $e) { + } + + return [ + Cache::STATS_HITS => null, + Cache::STATS_MISSES => null, + Cache::STATS_UPTIME => $uptime, + Cache::STATS_MEMORY_USAGE => $memoryUsage, + Cache::STATS_MEMORY_AVAILABLE => null, + ]; + } + + /** + * Check if the document is expired. + */ + private function isExpired(BSONDocument $document) : bool + { + return isset($document[MongoDBCache::EXPIRATION_FIELD]) && + $document[MongoDBCache::EXPIRATION_FIELD] instanceof UTCDateTime && + $document[MongoDBCache::EXPIRATION_FIELD]->toDateTime() < new DateTime(); + } + + private function createExpirationIndex() : void + { + if ($this->expirationIndexCreated) { + return; + } + + $this->collection->createIndex([MongoDBCache::EXPIRATION_FIELD => 1], ['background' => true, 'expireAfterSeconds' => 0]); + } +} diff --git a/vendor/doctrine/cache/lib/Doctrine/Common/Cache/FileCache.php b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/FileCache.php new file mode 100644 index 000000000..4b485205c --- /dev/null +++ b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/FileCache.php @@ -0,0 +1,281 @@ +umask = $umask; + + if (! $this->createPathIfNeeded($directory)) { + throw new InvalidArgumentException(sprintf( + 'The directory "%s" does not exist and could not be created.', + $directory + )); + } + + if (! is_writable($directory)) { + throw new InvalidArgumentException(sprintf( + 'The directory "%s" is not writable.', + $directory + )); + } + + // YES, this needs to be *after* createPathIfNeeded() + $this->directory = realpath($directory); + $this->extension = (string) $extension; + + $this->directoryStringLength = strlen($this->directory); + $this->extensionStringLength = strlen($this->extension); + $this->isRunningOnWindows = defined('PHP_WINDOWS_VERSION_BUILD'); + } + + /** + * Gets the cache directory. + * + * @return string + */ + public function getDirectory() + { + return $this->directory; + } + + /** + * Gets the cache file extension. + * + * @return string + */ + public function getExtension() + { + return $this->extension; + } + + /** + * @param string $id + * + * @return string + */ + protected function getFilename($id) + { + $hash = hash('sha256', $id); + + // This ensures that the filename is unique and that there are no invalid chars in it. + if ($id === '' + || ((strlen($id) * 2 + $this->extensionStringLength) > 255) + || ($this->isRunningOnWindows && ($this->directoryStringLength + 4 + strlen($id) * 2 + $this->extensionStringLength) > 258) + ) { + // Most filesystems have a limit of 255 chars for each path component. On Windows the the whole path is limited + // to 260 chars (including terminating null char). Using long UNC ("\\?\" prefix) does not work with the PHP API. + // And there is a bug in PHP (https://bugs.php.net/bug.php?id=70943) with path lengths of 259. + // So if the id in hex representation would surpass the limit, we use the hash instead. The prefix prevents + // collisions between the hash and bin2hex. + $filename = '_' . $hash; + } else { + $filename = bin2hex($id); + } + + return $this->directory + . DIRECTORY_SEPARATOR + . substr($hash, 0, 2) + . DIRECTORY_SEPARATOR + . $filename + . $this->extension; + } + + /** + * {@inheritdoc} + */ + protected function doDelete($id) + { + $filename = $this->getFilename($id); + + return @unlink($filename) || ! file_exists($filename); + } + + /** + * {@inheritdoc} + */ + protected function doFlush() + { + foreach ($this->getIterator() as $name => $file) { + if ($file->isDir()) { + // Remove the intermediate directories which have been created to balance the tree. It only takes effect + // if the directory is empty. If several caches share the same directory but with different file extensions, + // the other ones are not removed. + @rmdir($name); + } elseif ($this->isFilenameEndingWithExtension($name)) { + // If an extension is set, only remove files which end with the given extension. + // If no extension is set, we have no other choice than removing everything. + @unlink($name); + } + } + + return true; + } + + /** + * {@inheritdoc} + */ + protected function doGetStats() + { + $usage = 0; + foreach ($this->getIterator() as $name => $file) { + if ($file->isDir() || ! $this->isFilenameEndingWithExtension($name)) { + continue; + } + + $usage += $file->getSize(); + } + + $free = disk_free_space($this->directory); + + return [ + Cache::STATS_HITS => null, + Cache::STATS_MISSES => null, + Cache::STATS_UPTIME => null, + Cache::STATS_MEMORY_USAGE => $usage, + Cache::STATS_MEMORY_AVAILABLE => $free, + ]; + } + + /** + * Create path if needed. + * + * @return bool TRUE on success or if path already exists, FALSE if path cannot be created. + */ + private function createPathIfNeeded(string $path) : bool + { + if (! is_dir($path)) { + if (@mkdir($path, 0777 & (~$this->umask), true) === false && ! is_dir($path)) { + return false; + } + } + + return true; + } + + /** + * Writes a string content to file in an atomic way. + * + * @param string $filename Path to the file where to write the data. + * @param string $content The content to write + * + * @return bool TRUE on success, FALSE if path cannot be created, if path is not writable or an any other error. + */ + protected function writeFile(string $filename, string $content) : bool + { + $filepath = pathinfo($filename, PATHINFO_DIRNAME); + + if (! $this->createPathIfNeeded($filepath)) { + return false; + } + + if (! is_writable($filepath)) { + return false; + } + + $tmpFile = tempnam($filepath, 'swap'); + @chmod($tmpFile, 0666 & (~$this->umask)); + + if (file_put_contents($tmpFile, $content) !== false) { + @chmod($tmpFile, 0666 & (~$this->umask)); + if (@rename($tmpFile, $filename)) { + return true; + } + + @unlink($tmpFile); + } + + return false; + } + + private function getIterator() : Iterator + { + return new RecursiveIteratorIterator( + new RecursiveDirectoryIterator($this->directory, FilesystemIterator::SKIP_DOTS), + RecursiveIteratorIterator::CHILD_FIRST + ); + } + + /** + * @param string $name The filename + */ + private function isFilenameEndingWithExtension(string $name) : bool + { + return $this->extension === '' + || strrpos($name, $this->extension) === (strlen($name) - $this->extensionStringLength); + } +} diff --git a/vendor/doctrine/cache/lib/Doctrine/Common/Cache/FilesystemCache.php b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/FilesystemCache.php new file mode 100644 index 000000000..8f34c9cb4 --- /dev/null +++ b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/FilesystemCache.php @@ -0,0 +1,102 @@ +getFilename($id); + + if (! is_file($filename)) { + return false; + } + + $resource = fopen($filename, 'r'); + $line = fgets($resource); + + if ($line !== false) { + $lifetime = (int) $line; + } + + if ($lifetime !== 0 && $lifetime < time()) { + fclose($resource); + + return false; + } + + while (($line = fgets($resource)) !== false) { + $data .= $line; + } + + fclose($resource); + + return unserialize($data); + } + + /** + * {@inheritdoc} + */ + protected function doContains($id) + { + $lifetime = -1; + $filename = $this->getFilename($id); + + if (! is_file($filename)) { + return false; + } + + $resource = fopen($filename, 'r'); + $line = fgets($resource); + + if ($line !== false) { + $lifetime = (int) $line; + } + + fclose($resource); + + return $lifetime === 0 || $lifetime > time(); + } + + /** + * {@inheritdoc} + */ + protected function doSave($id, $data, $lifeTime = 0) + { + if ($lifeTime > 0) { + $lifeTime = time() + $lifeTime; + } + + $data = serialize($data); + $filename = $this->getFilename($id); + + return $this->writeFile($filename, $lifeTime . PHP_EOL . $data); + } +} diff --git a/vendor/doctrine/cache/lib/Doctrine/Common/Cache/FlushableCache.php b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/FlushableCache.php new file mode 100644 index 000000000..ee7ce984e --- /dev/null +++ b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/FlushableCache.php @@ -0,0 +1,18 @@ +collection = $collection; + } + + /** + * {@inheritdoc} + */ + protected function doFetch($id) + { + $document = $this->collection->findOne(['_id' => $id], [MongoDBCache::DATA_FIELD, MongoDBCache::EXPIRATION_FIELD]); + + if ($document === null) { + return false; + } + + if ($this->isExpired($document)) { + $this->createExpirationIndex(); + $this->doDelete($id); + + return false; + } + + return unserialize($document[MongoDBCache::DATA_FIELD]->bin); + } + + /** + * {@inheritdoc} + */ + protected function doContains($id) + { + $document = $this->collection->findOne(['_id' => $id], [MongoDBCache::EXPIRATION_FIELD]); + + if ($document === null) { + return false; + } + + if ($this->isExpired($document)) { + $this->createExpirationIndex(); + $this->doDelete($id); + + return false; + } + + return true; + } + + /** + * {@inheritdoc} + */ + protected function doSave($id, $data, $lifeTime = 0) + { + try { + $result = $this->collection->update( + ['_id' => $id], + [ + '$set' => [ + MongoDBCache::EXPIRATION_FIELD => ($lifeTime > 0 ? new MongoDate(time() + $lifeTime) : null), + MongoDBCache::DATA_FIELD => new MongoBinData(serialize($data), MongoBinData::BYTE_ARRAY), + ], + ], + ['upsert' => true, 'multiple' => false] + ); + } catch (MongoCursorException $e) { + return false; + } + + return ($result['ok'] ?? 1) == 1; + } + + /** + * {@inheritdoc} + */ + protected function doDelete($id) + { + $result = $this->collection->remove(['_id' => $id]); + + return ($result['ok'] ?? 1) == 1; + } + + /** + * {@inheritdoc} + */ + protected function doFlush() + { + // Use remove() in lieu of drop() to maintain any collection indexes + $result = $this->collection->remove(); + + return ($result['ok'] ?? 1) == 1; + } + + /** + * {@inheritdoc} + */ + protected function doGetStats() + { + $serverStatus = $this->collection->db->command([ + 'serverStatus' => 1, + 'locks' => 0, + 'metrics' => 0, + 'recordStats' => 0, + 'repl' => 0, + ]); + + $collStats = $this->collection->db->command(['collStats' => 1]); + + return [ + Cache::STATS_HITS => null, + Cache::STATS_MISSES => null, + Cache::STATS_UPTIME => $serverStatus['uptime'] ?? null, + Cache::STATS_MEMORY_USAGE => $collStats['size'] ?? null, + Cache::STATS_MEMORY_AVAILABLE => null, + ]; + } + + /** + * Check if the document is expired. + * + * @param array $document + */ + private function isExpired(array $document) : bool + { + return isset($document[MongoDBCache::EXPIRATION_FIELD]) && + $document[MongoDBCache::EXPIRATION_FIELD] instanceof MongoDate && + $document[MongoDBCache::EXPIRATION_FIELD]->sec < time(); + } + + private function createExpirationIndex() : void + { + if ($this->expirationIndexCreated) { + return; + } + + $this->expirationIndexCreated = true; + $this->collection->createIndex([MongoDBCache::EXPIRATION_FIELD => 1], ['background' => true, 'expireAfterSeconds' => 0]); + } +} diff --git a/vendor/doctrine/cache/lib/Doctrine/Common/Cache/MemcacheCache.php b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/MemcacheCache.php new file mode 100644 index 000000000..42bbd2cea --- /dev/null +++ b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/MemcacheCache.php @@ -0,0 +1,104 @@ +memcache = $memcache; + } + + /** + * Gets the memcache instance used by the cache. + * + * @return Memcache|null + */ + public function getMemcache() + { + return $this->memcache; + } + + /** + * {@inheritdoc} + */ + protected function doFetch($id) + { + return $this->memcache->get($id); + } + + /** + * {@inheritdoc} + */ + protected function doContains($id) + { + $flags = null; + $this->memcache->get($id, $flags); + + //if memcache has changed the value of "flags", it means the value exists + return $flags !== null; + } + + /** + * {@inheritdoc} + */ + protected function doSave($id, $data, $lifeTime = 0) + { + if ($lifeTime > 30 * 24 * 3600) { + $lifeTime = time() + $lifeTime; + } + + return $this->memcache->set($id, $data, 0, (int) $lifeTime); + } + + /** + * {@inheritdoc} + */ + protected function doDelete($id) + { + // Memcache::delete() returns false if entry does not exist + return $this->memcache->delete($id) || ! $this->doContains($id); + } + + /** + * {@inheritdoc} + */ + protected function doFlush() + { + return $this->memcache->flush(); + } + + /** + * {@inheritdoc} + */ + protected function doGetStats() + { + $stats = $this->memcache->getStats(); + + return [ + Cache::STATS_HITS => $stats['get_hits'], + Cache::STATS_MISSES => $stats['get_misses'], + Cache::STATS_UPTIME => $stats['uptime'], + Cache::STATS_MEMORY_USAGE => $stats['bytes'], + Cache::STATS_MEMORY_AVAILABLE => $stats['limit_maxbytes'], + ]; + } +} diff --git a/vendor/doctrine/cache/lib/Doctrine/Common/Cache/MemcachedCache.php b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/MemcachedCache.php new file mode 100644 index 000000000..da966ae26 --- /dev/null +++ b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/MemcachedCache.php @@ -0,0 +1,170 @@ +memcached = $memcached; + } + + /** + * Gets the memcached instance used by the cache. + * + * @return Memcached|null + */ + public function getMemcached() + { + return $this->memcached; + } + + /** + * {@inheritdoc} + */ + protected function doFetch($id) + { + return $this->memcached->get($id); + } + + /** + * {@inheritdoc} + */ + protected function doFetchMultiple(array $keys) + { + return $this->memcached->getMulti($keys) ?: []; + } + + /** + * {@inheritdoc} + */ + protected function doSaveMultiple(array $keysAndValues, $lifetime = 0) + { + foreach (array_keys($keysAndValues) as $id) { + $this->validateCacheId($id); + } + + if ($lifetime > 30 * 24 * 3600) { + $lifetime = time() + $lifetime; + } + + return $this->memcached->setMulti($keysAndValues, $lifetime); + } + + /** + * {@inheritdoc} + */ + protected function doContains($id) + { + $this->memcached->get($id); + + return $this->memcached->getResultCode() === Memcached::RES_SUCCESS; + } + + /** + * {@inheritdoc} + */ + protected function doSave($id, $data, $lifeTime = 0) + { + $this->validateCacheId($id); + + if ($lifeTime > 30 * 24 * 3600) { + $lifeTime = time() + $lifeTime; + } + + return $this->memcached->set($id, $data, (int) $lifeTime); + } + + /** + * {@inheritdoc} + */ + protected function doDeleteMultiple(array $keys) + { + return $this->memcached->deleteMulti($keys) + || $this->memcached->getResultCode() === Memcached::RES_NOTFOUND; + } + + /** + * {@inheritdoc} + */ + protected function doDelete($id) + { + return $this->memcached->delete($id) + || $this->memcached->getResultCode() === Memcached::RES_NOTFOUND; + } + + /** + * {@inheritdoc} + */ + protected function doFlush() + { + return $this->memcached->flush(); + } + + /** + * {@inheritdoc} + */ + protected function doGetStats() + { + $stats = $this->memcached->getStats(); + $servers = $this->memcached->getServerList(); + $key = $servers[0]['host'] . ':' . $servers[0]['port']; + $stats = $stats[$key]; + + return [ + Cache::STATS_HITS => $stats['get_hits'], + Cache::STATS_MISSES => $stats['get_misses'], + Cache::STATS_UPTIME => $stats['uptime'], + Cache::STATS_MEMORY_USAGE => $stats['bytes'], + Cache::STATS_MEMORY_AVAILABLE => $stats['limit_maxbytes'], + ]; + } + + /** + * Validate the cache id + * + * @see https://github.com/memcached/memcached/blob/1.5.12/doc/protocol.txt#L41-L49 + * + * @param string $id + * + * @return void + * + * @throws InvalidCacheId + */ + private function validateCacheId($id) + { + if (strlen($id) > self::CACHE_ID_MAX_LENGTH) { + throw InvalidCacheId::exceedsMaxLength($id, self::CACHE_ID_MAX_LENGTH); + } + + if (strpos($id, ' ') !== false) { + throw InvalidCacheId::containsUnauthorizedCharacter($id, ' '); + } + + if (preg_match('/[\t\r\n]/', $id) === 1) { + throw InvalidCacheId::containsControlCharacter($id); + } + } +} diff --git a/vendor/doctrine/cache/lib/Doctrine/Common/Cache/MongoDBCache.php b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/MongoDBCache.php new file mode 100644 index 000000000..861e4dda3 --- /dev/null +++ b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/MongoDBCache.php @@ -0,0 +1,112 @@ +provider = new LegacyMongoDBCache($collection); + } elseif ($collection instanceof Collection) { + $this->provider = new ExtMongoDBCache($collection); + } else { + throw new InvalidArgumentException('Invalid collection given - expected a MongoCollection or MongoDB\Collection instance'); + } + } + + /** + * {@inheritdoc} + */ + protected function doFetch($id) + { + return $this->provider->doFetch($id); + } + + /** + * {@inheritdoc} + */ + protected function doContains($id) + { + return $this->provider->doContains($id); + } + + /** + * {@inheritdoc} + */ + protected function doSave($id, $data, $lifeTime = 0) + { + return $this->provider->doSave($id, $data, $lifeTime); + } + + /** + * {@inheritdoc} + */ + protected function doDelete($id) + { + return $this->provider->doDelete($id); + } + + /** + * {@inheritdoc} + */ + protected function doFlush() + { + return $this->provider->doFlush(); + } + + /** + * {@inheritdoc} + */ + protected function doGetStats() + { + return $this->provider->doGetStats(); + } +} diff --git a/vendor/doctrine/cache/lib/Doctrine/Common/Cache/MultiDeleteCache.php b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/MultiDeleteCache.php new file mode 100644 index 000000000..d099d47b8 --- /dev/null +++ b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/MultiDeleteCache.php @@ -0,0 +1,22 @@ + infinite lifeTime). + * + * @return bool TRUE if the operation was successful, FALSE if it wasn't. + */ + public function saveMultiple(array $keysAndValues, $lifetime = 0); +} diff --git a/vendor/doctrine/cache/lib/Doctrine/Common/Cache/PhpFileCache.php b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/PhpFileCache.php new file mode 100644 index 000000000..c0619991c --- /dev/null +++ b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/PhpFileCache.php @@ -0,0 +1,118 @@ +includeFileForId($id); + + if ($value === null) { + return false; + } + + if ($value['lifetime'] !== 0 && $value['lifetime'] < time()) { + return false; + } + + return $value['data']; + } + + /** + * {@inheritdoc} + */ + protected function doContains($id) + { + $value = $this->includeFileForId($id); + + if ($value === null) { + return false; + } + + return $value['lifetime'] === 0 || $value['lifetime'] > time(); + } + + /** + * {@inheritdoc} + */ + protected function doSave($id, $data, $lifeTime = 0) + { + if ($lifeTime > 0) { + $lifeTime = time() + $lifeTime; + } + + $filename = $this->getFilename($id); + + $value = [ + 'lifetime' => $lifeTime, + 'data' => $data, + ]; + + if (is_object($data) && method_exists($data, '__set_state')) { + $value = var_export($value, true); + $code = sprintf('writeFile($filename, $code); + } + + /** + * @return array|null + */ + private function includeFileForId(string $id) : ?array + { + $fileName = $this->getFilename($id); + + // note: error suppression is still faster than `file_exists`, `is_file` and `is_readable` + set_error_handler(self::$emptyErrorHandler); + + $value = include $fileName; + + restore_error_handler(); + + if (! isset($value['lifetime'])) { + return null; + } + + return $value; + } +} diff --git a/vendor/doctrine/cache/lib/Doctrine/Common/Cache/PredisCache.php b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/PredisCache.php new file mode 100644 index 000000000..6430d52d7 --- /dev/null +++ b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/PredisCache.php @@ -0,0 +1,143 @@ +client = $client; + } + + /** + * {@inheritdoc} + */ + protected function doFetch($id) + { + $result = $this->client->get($id); + if ($result === null) { + return false; + } + + return unserialize($result); + } + + /** + * {@inheritdoc} + */ + protected function doFetchMultiple(array $keys) + { + $fetchedItems = call_user_func_array([$this->client, 'mget'], $keys); + + return array_map('unserialize', array_filter(array_combine($keys, $fetchedItems))); + } + + /** + * {@inheritdoc} + */ + protected function doSaveMultiple(array $keysAndValues, $lifetime = 0) + { + if ($lifetime) { + $success = true; + + // Keys have lifetime, use SETEX for each of them + foreach ($keysAndValues as $key => $value) { + $response = (string) $this->client->setex($key, $lifetime, serialize($value)); + + if ($response == 'OK') { + continue; + } + + $success = false; + } + + return $success; + } + + // No lifetime, use MSET + $response = $this->client->mset(array_map(static function ($value) { + return serialize($value); + }, $keysAndValues)); + + return (string) $response == 'OK'; + } + + /** + * {@inheritdoc} + */ + protected function doContains($id) + { + return (bool) $this->client->exists($id); + } + + /** + * {@inheritdoc} + */ + protected function doSave($id, $data, $lifeTime = 0) + { + $data = serialize($data); + if ($lifeTime > 0) { + $response = $this->client->setex($id, $lifeTime, $data); + } else { + $response = $this->client->set($id, $data); + } + + return $response === true || $response == 'OK'; + } + + /** + * {@inheritdoc} + */ + protected function doDelete($id) + { + return $this->client->del($id) >= 0; + } + + /** + * {@inheritdoc} + */ + protected function doDeleteMultiple(array $keys) + { + return $this->client->del($keys) >= 0; + } + + /** + * {@inheritdoc} + */ + protected function doFlush() + { + $response = $this->client->flushdb(); + + return $response === true || $response == 'OK'; + } + + /** + * {@inheritdoc} + */ + protected function doGetStats() + { + $info = $this->client->info(); + + return [ + Cache::STATS_HITS => $info['Stats']['keyspace_hits'], + Cache::STATS_MISSES => $info['Stats']['keyspace_misses'], + Cache::STATS_UPTIME => $info['Server']['uptime_in_seconds'], + Cache::STATS_MEMORY_USAGE => $info['Memory']['used_memory'], + Cache::STATS_MEMORY_AVAILABLE => false, + ]; + } +} diff --git a/vendor/doctrine/cache/lib/Doctrine/Common/Cache/RedisCache.php b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/RedisCache.php new file mode 100644 index 000000000..324570583 --- /dev/null +++ b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/RedisCache.php @@ -0,0 +1,181 @@ +setOption(Redis::OPT_SERIALIZER, $this->getSerializerValue()); + $this->redis = $redis; + } + + /** + * Gets the redis instance used by the cache. + * + * @return Redis|null + */ + public function getRedis() + { + return $this->redis; + } + + /** + * {@inheritdoc} + */ + protected function doFetch($id) + { + return $this->redis->get($id); + } + + /** + * {@inheritdoc} + */ + protected function doFetchMultiple(array $keys) + { + $fetchedItems = array_combine($keys, $this->redis->mget($keys)); + + // Redis mget returns false for keys that do not exist. So we need to filter those out unless it's the real data. + $keysToFilter = array_keys(array_filter($fetchedItems, static function ($item) : bool { + return $item === false; + })); + + if ($keysToFilter) { + $multi = $this->redis->multi(Redis::PIPELINE); + foreach ($keysToFilter as $key) { + $multi->exists($key); + } + $existItems = array_filter($multi->exec()); + $missedItemKeys = array_diff_key($keysToFilter, $existItems); + $fetchedItems = array_diff_key($fetchedItems, array_fill_keys($missedItemKeys, true)); + } + + return $fetchedItems; + } + + /** + * {@inheritdoc} + */ + protected function doSaveMultiple(array $keysAndValues, $lifetime = 0) + { + if ($lifetime) { + // Keys have lifetime, use SETEX for each of them + $multi = $this->redis->multi(Redis::PIPELINE); + foreach ($keysAndValues as $key => $value) { + $multi->setex($key, $lifetime, $value); + } + $succeeded = array_filter($multi->exec()); + + return count($succeeded) == count($keysAndValues); + } + + // No lifetime, use MSET + return (bool) $this->redis->mset($keysAndValues); + } + + /** + * {@inheritdoc} + */ + protected function doContains($id) + { + $exists = $this->redis->exists($id); + + if (is_bool($exists)) { + return $exists; + } + + return $exists > 0; + } + + /** + * {@inheritdoc} + */ + protected function doSave($id, $data, $lifeTime = 0) + { + if ($lifeTime > 0) { + return $this->redis->setex($id, $lifeTime, $data); + } + + return $this->redis->set($id, $data); + } + + /** + * {@inheritdoc} + */ + protected function doDelete($id) + { + return $this->redis->del($id) >= 0; + } + + /** + * {@inheritdoc} + */ + protected function doDeleteMultiple(array $keys) + { + return $this->redis->del($keys) >= 0; + } + + /** + * {@inheritdoc} + */ + protected function doFlush() + { + return $this->redis->flushDB(); + } + + /** + * {@inheritdoc} + */ + protected function doGetStats() + { + $info = $this->redis->info(); + + return [ + Cache::STATS_HITS => $info['keyspace_hits'], + Cache::STATS_MISSES => $info['keyspace_misses'], + Cache::STATS_UPTIME => $info['uptime_in_seconds'], + Cache::STATS_MEMORY_USAGE => $info['used_memory'], + Cache::STATS_MEMORY_AVAILABLE => false, + ]; + } + + /** + * Returns the serializer constant to use. If Redis is compiled with + * igbinary support, that is used. Otherwise the default PHP serializer is + * used. + * + * @return int One of the Redis::SERIALIZER_* constants + */ + protected function getSerializerValue() + { + if (defined('Redis::SERIALIZER_IGBINARY') && extension_loaded('igbinary')) { + return Redis::SERIALIZER_IGBINARY; + } + + return Redis::SERIALIZER_PHP; + } +} diff --git a/vendor/doctrine/cache/lib/Doctrine/Common/Cache/SQLite3Cache.php b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/SQLite3Cache.php new file mode 100644 index 000000000..56deb0774 --- /dev/null +++ b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/SQLite3Cache.php @@ -0,0 +1,206 @@ +sqlite = $sqlite; + $this->table = (string) $table; + + $this->ensureTableExists(); + } + + private function ensureTableExists() : void + { + $this->sqlite->exec( + sprintf( + 'CREATE TABLE IF NOT EXISTS %s(%s TEXT PRIMARY KEY NOT NULL, %s BLOB, %s INTEGER)', + $this->table, + static::ID_FIELD, + static::DATA_FIELD, + static::EXPIRATION_FIELD + ) + ); + } + + /** + * {@inheritdoc} + */ + protected function doFetch($id) + { + $item = $this->findById($id); + + if (! $item) { + return false; + } + + return unserialize($item[self::DATA_FIELD]); + } + + /** + * {@inheritdoc} + */ + protected function doContains($id) + { + return $this->findById($id, false) !== null; + } + + /** + * {@inheritdoc} + */ + protected function doSave($id, $data, $lifeTime = 0) + { + $statement = $this->sqlite->prepare(sprintf( + 'INSERT OR REPLACE INTO %s (%s) VALUES (:id, :data, :expire)', + $this->table, + implode(',', $this->getFields()) + )); + + $statement->bindValue(':id', $id); + $statement->bindValue(':data', serialize($data), SQLITE3_BLOB); + $statement->bindValue(':expire', $lifeTime > 0 ? time() + $lifeTime : null); + + return $statement->execute() instanceof SQLite3Result; + } + + /** + * {@inheritdoc} + */ + protected function doDelete($id) + { + [$idField] = $this->getFields(); + + $statement = $this->sqlite->prepare(sprintf( + 'DELETE FROM %s WHERE %s = :id', + $this->table, + $idField + )); + + $statement->bindValue(':id', $id); + + return $statement->execute() instanceof SQLite3Result; + } + + /** + * {@inheritdoc} + */ + protected function doFlush() + { + return $this->sqlite->exec(sprintf('DELETE FROM %s', $this->table)); + } + + /** + * {@inheritdoc} + */ + protected function doGetStats() + { + // no-op. + } + + /** + * Find a single row by ID. + * + * @param mixed $id + * + * @return array|null + */ + private function findById($id, bool $includeData = true) : ?array + { + [$idField] = $fields = $this->getFields(); + + if (! $includeData) { + $key = array_search(static::DATA_FIELD, $fields); + unset($fields[$key]); + } + + $statement = $this->sqlite->prepare(sprintf( + 'SELECT %s FROM %s WHERE %s = :id LIMIT 1', + implode(',', $fields), + $this->table, + $idField + )); + + $statement->bindValue(':id', $id, SQLITE3_TEXT); + + $item = $statement->execute()->fetchArray(SQLITE3_ASSOC); + + if ($item === false) { + return null; + } + + if ($this->isExpired($item)) { + $this->doDelete($id); + + return null; + } + + return $item; + } + + /** + * Gets an array of the fields in our table. + * + * @return array + */ + private function getFields() : array + { + return [static::ID_FIELD, static::DATA_FIELD, static::EXPIRATION_FIELD]; + } + + /** + * Check if the item is expired. + * + * @param array $item + */ + private function isExpired(array $item) : bool + { + return isset($item[static::EXPIRATION_FIELD]) && + $item[self::EXPIRATION_FIELD] !== null && + $item[self::EXPIRATION_FIELD] < time(); + } +} diff --git a/vendor/doctrine/cache/lib/Doctrine/Common/Cache/Version.php b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/Version.php new file mode 100644 index 000000000..834b5b7d4 --- /dev/null +++ b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/Version.php @@ -0,0 +1,8 @@ + $info['total_hit_count'], + Cache::STATS_MISSES => $info['total_miss_count'], + Cache::STATS_UPTIME => $info['total_cache_uptime'], + Cache::STATS_MEMORY_USAGE => $meminfo['memory_total'], + Cache::STATS_MEMORY_AVAILABLE => $meminfo['memory_free'], + ]; + } +} diff --git a/vendor/doctrine/cache/lib/Doctrine/Common/Cache/XcacheCache.php b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/XcacheCache.php new file mode 100644 index 000000000..9b3a485ed --- /dev/null +++ b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/XcacheCache.php @@ -0,0 +1,104 @@ +doContains($id) ? unserialize(xcache_get($id)) : false; + } + + /** + * {@inheritdoc} + */ + protected function doContains($id) + { + return xcache_isset($id); + } + + /** + * {@inheritdoc} + */ + protected function doSave($id, $data, $lifeTime = 0) + { + return xcache_set($id, serialize($data), (int) $lifeTime); + } + + /** + * {@inheritdoc} + */ + protected function doDelete($id) + { + return xcache_unset($id); + } + + /** + * {@inheritdoc} + */ + protected function doFlush() + { + $this->checkAuthorization(); + + xcache_clear_cache(XC_TYPE_VAR); + + return true; + } + + /** + * Checks that xcache.admin.enable_auth is Off. + * + * @return void + * + * @throws BadMethodCallException When xcache.admin.enable_auth is On. + */ + protected function checkAuthorization() + { + if (ini_get('xcache.admin.enable_auth')) { + throw new BadMethodCallException( + 'To use all features of \Doctrine\Common\Cache\XcacheCache, ' + . 'you must set "xcache.admin.enable_auth" to "Off" in your php.ini.' + ); + } + } + + /** + * {@inheritdoc} + */ + protected function doGetStats() + { + $this->checkAuthorization(); + + $info = xcache_info(XC_TYPE_VAR, 0); + + return [ + Cache::STATS_HITS => $info['hits'], + Cache::STATS_MISSES => $info['misses'], + Cache::STATS_UPTIME => null, + Cache::STATS_MEMORY_USAGE => $info['size'], + Cache::STATS_MEMORY_AVAILABLE => $info['avail'], + ]; + } +} diff --git a/vendor/doctrine/cache/lib/Doctrine/Common/Cache/ZendDataCache.php b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/ZendDataCache.php new file mode 100644 index 000000000..a6b67ef8b --- /dev/null +++ b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/ZendDataCache.php @@ -0,0 +1,69 @@ +getNamespace(); + if (empty($namespace)) { + return zend_shm_cache_clear(); + } + + return zend_shm_cache_clear($namespace); + } + + /** + * {@inheritdoc} + */ + protected function doGetStats() + { + return null; + } +} diff --git a/vendor/doctrine/collections/.doctrine-project.json b/vendor/doctrine/collections/.doctrine-project.json new file mode 100644 index 000000000..57d2feffb --- /dev/null +++ b/vendor/doctrine/collections/.doctrine-project.json @@ -0,0 +1,17 @@ +{ + "active": true, + "name": "Collections", + "slug": "collections", + "docsSlug": "doctrine-collections", + "versions": [ + { + "name": "master", + "branchName": "master", + "slug": "latest", + "aliases": [ + "current", + "stable" + ] + } + ] +} diff --git a/vendor/doctrine/collections/CONTRIBUTING.md b/vendor/doctrine/collections/CONTRIBUTING.md new file mode 100644 index 000000000..83437dab5 --- /dev/null +++ b/vendor/doctrine/collections/CONTRIBUTING.md @@ -0,0 +1,54 @@ +# Contribute to Doctrine + +Thank you for contributing to Doctrine! + +Before we can merge your Pull-Request here are some guidelines that you need to follow. +These guidelines exist not to annoy you, but to keep the code base clean, +unified and future proof. + +## We only accept PRs to "master" + +Our branching strategy is "everything to master first", even +bugfixes and we then merge them into the stable branches. You should only +open pull requests against the master branch. Otherwise we cannot accept the PR. + +There is one exception to the rule, when we merged a bug into some stable branches +we do occasionally accept pull requests that merge the same bug fix into earlier +branches. + +## Coding Standard + +We use the [Doctrine Coding Standard](https://github.com/doctrine/coding-standard). + +## Unit-Tests + +Please try to add a test for your pull-request. + +* If you want to contribute new functionality add unit- or functional tests + depending on the scope of the feature. + +You can run the unit-tests by calling ``vendor/bin/phpunit`` from the root of the project. +It will run all the project tests. + +In order to do that, you will need a fresh copy of doctrine/collections, and you +will have to run a composer installation in the project: + +```sh +git clone git@github.com:doctrine/collections.git +cd collections +curl -sS https://getcomposer.org/installer | php -- +./composer.phar install +``` + +## Travis + +We automatically run your pull request through [Travis CI](https://www.travis-ci.org) +against supported PHP versions. If you break the tests, we cannot merge your code, +so please make sure that your code is working before opening up a Pull-Request. + +## Getting merged + +Please allow us time to review your pull requests. We will give our best to review +everything as fast as possible, but cannot always live up to our own expectations. + +Thank you very much again for your contribution! diff --git a/vendor/doctrine/collections/LICENSE b/vendor/doctrine/collections/LICENSE new file mode 100644 index 000000000..5e781fce4 --- /dev/null +++ b/vendor/doctrine/collections/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2006-2013 Doctrine Project + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/doctrine/collections/README.md b/vendor/doctrine/collections/README.md new file mode 100644 index 000000000..d089e5132 --- /dev/null +++ b/vendor/doctrine/collections/README.md @@ -0,0 +1,92 @@ +# Doctrine Collections + +[![Build Status](https://travis-ci.org/doctrine/collections.svg?branch=master)](https://travis-ci.org/doctrine/collections) +[![Code Coverage](https://codecov.io/gh/doctrine/collections/branch/master/graph/badge.svg)](https://codecov.io/gh/doctrine/collections/branch/master) + +Collections Abstraction library + +## Changelog + +### v1.6.1 + +This release, combined with the release of [`doctrine/collections` `v1.6.1`](https://github.com/doctrine/collections/releases/tag/v1.6.1), +fixes an issue where parsing annotations was not possible +for classes within `doctrine/collections`. + +Specifically, `v1.6.0` introduced Psalm-specific annotations +such as (for example) `@template` and `@template-implements`, +which were both incorrectly recognized as `@template`. + +`@template` has therefore been removed, and instead we use +the prefixed `@psalm-template`, which is no longer parsed +by `doctrine/collections` `v1.6.1` + +Total issues resolved: **1** + +- [186: Use `@psalm-template` annotation to avoid clashes](https://github.com/doctrine/collections/pull/186) thanks to @muglug + +### v1.6.0 + +This release bumps the minimum required PHP version to 7.1.3. + +Following improvements were introduced: + + * `ArrayCollection#filter()` now allows filtering by key, value or both. + * When using the `ClosureExpressionVisitor` over objects with a defined + accessor and property, the accessor is prioritised. + * Updated testing tools and coding standards, autoloading, which also + led to marginal performance improvements + * Introduced generic type docblock declarations from [psalm](https://github.com/vimeo/psalm), + which should allow users to declare `/** @var Collection */` + in their code, and leverage the type propagation deriving from that. + +Total issues resolved: **16** + +- [127: Use PSR-4](https://github.com/doctrine/collections/pull/127) thanks to @Nyholm +- [129: Remove space in method declaration](https://github.com/doctrine/collections/pull/129) thanks to @bounoable +- [130: Update build to add PHPCS and PHPStan](https://github.com/doctrine/collections/pull/130) thanks to @lcobucci +- [131: ClosureExpressionVisitor > Don't duplicate the accessor when the field already starts with it](https://github.com/doctrine/collections/pull/131) thanks to @ruudk +- [139: Apply Doctrine CS 2.1](https://github.com/doctrine/collections/pull/139) thanks to @Majkl578 +- [142: CS 4.0, version composer.lock, merge stages](https://github.com/doctrine/collections/pull/142) thanks to @Majkl578 +- [144: Update to PHPUnit 7](https://github.com/doctrine/collections/pull/144) thanks to @carusogabriel +- [146: Update changelog for v1.4.0 and v1.5.0](https://github.com/doctrine/collections/pull/146) thanks to @GromNaN +- [154: Update index.rst](https://github.com/doctrine/collections/pull/154) thanks to @chraiet +- [158: Extract Selectable method into own documentation section](https://github.com/doctrine/collections/pull/158) thanks to @SenseException +- [160: Update homepage](https://github.com/doctrine/collections/pull/160) thanks to @Majkl578 +- [165: Allow `ArrayCollection#filter()` to filter by key, value or both](https://github.com/doctrine/collections/issues/165) thanks to @0x13a +- [167: Allow `ArrayCollection#filter()` to filter by key and also value](https://github.com/doctrine/collections/pull/167) thanks to @0x13a +- [175: CI: Test against PHP 7.4snapshot instead of nightly (8.0)](https://github.com/doctrine/collections/pull/175) thanks to @Majkl578 +- [177: Generify collections using Psalm](https://github.com/doctrine/collections/pull/177) thanks to @nschoellhorn +- [178: Updated doctrine/coding-standard to 6.0](https://github.com/doctrine/collections/pull/178) thanks to @patrickjahns + +### v1.5.0 + +* [Require PHP 7.1+](https://github.com/doctrine/collections/pull/105) +* [Drop HHVM support](https://github.com/doctrine/collections/pull/118) + +### v1.4.0 + +* [Require PHP 5.6+](https://github.com/doctrine/collections/pull/105) +* [Add `ArrayCollection::createFrom()`](https://github.com/doctrine/collections/pull/91) +* [Support non-camel-case naming](https://github.com/doctrine/collections/pull/57) +* [Comparison `START_WITH`, `END_WITH`](https://github.com/doctrine/collections/pull/78) +* [Comparison `MEMBER_OF`](https://github.com/doctrine/collections/pull/66) +* [Add Contributing guide](https://github.com/doctrine/collections/pull/103) + +### v1.3.0 + +* [Explicit casting of first and max results in criteria API](https://github.com/doctrine/collections/pull/26) +* [Keep keys when using `ArrayCollection#matching()` with sorting](https://github.com/doctrine/collections/pull/49) +* [Made `AbstractLazyCollection#$initialized` protected for extensibility](https://github.com/doctrine/collections/pull/52) + +### v1.2.0 + +* Add a new ``AbstractLazyCollection`` + +### v1.1.0 + +* Deprecated ``Comparison::IS``, because it's only there for SQL semantics. + These are fixed in the ORM instead. +* Add ``Comparison::CONTAINS`` to perform partial string matches: + + $criteria->andWhere($criteria->expr()->contains('property', 'Foo')); diff --git a/vendor/doctrine/collections/composer.json b/vendor/doctrine/collections/composer.json new file mode 100644 index 000000000..f0b6a4a21 --- /dev/null +++ b/vendor/doctrine/collections/composer.json @@ -0,0 +1,37 @@ +{ + "name": "doctrine/collections", + "type": "library", + "description": "PHP Doctrine Collections library that adds additional functionality on top of PHP arrays.", + "keywords": [ + "php", + "collections", + "array", + "iterators" + ], + "homepage": "https://www.doctrine-project.org/projects/collections.html", + "license": "MIT", + "authors": [ + {"name": "Guilherme Blanco", "email": "guilhermeblanco@gmail.com"}, + {"name": "Roman Borschel", "email": "roman@code-factory.org"}, + {"name": "Benjamin Eberlei", "email": "kontakt@beberlei.de"}, + {"name": "Jonathan Wage", "email": "jonwage@gmail.com"}, + {"name": "Johannes Schmitt", "email": "schmittjoh@gmail.com"} + ], + "require": { + "php": "^7.1.3 || ^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^7.0", + "doctrine/coding-standard": "^6.0", + "phpstan/phpstan-shim": "^0.9.2", + "vimeo/psalm": "^3.8.1" + }, + "autoload": { + "psr-4": { "Doctrine\\Common\\Collections\\": "lib/Doctrine/Common/Collections" } + }, + "autoload-dev": { + "psr-4": { + "Doctrine\\Tests\\": "tests/Doctrine/Tests" + } + } +} diff --git a/vendor/doctrine/collections/docs/en/derived-collections.rst b/vendor/doctrine/collections/docs/en/derived-collections.rst new file mode 100644 index 000000000..03d9da5d6 --- /dev/null +++ b/vendor/doctrine/collections/docs/en/derived-collections.rst @@ -0,0 +1,26 @@ +Derived Collections +=================== + +You can create custom collection classes by extending the +``Doctrine\Common\Collections\ArrayCollection`` class. If the +``__construct`` semantics are different from the default ``ArrayCollection`` +you can override the ``createFrom`` method: + +.. code-block:: php + final class DerivedArrayCollection extends ArrayCollection + { + /** @var \stdClass */ + private $foo; + + public function __construct(\stdClass $foo, array $elements = []) + { + $this->foo = $foo; + + parent::__construct($elements); + } + + protected function createFrom(array $elements) : self + { + return new static($this->foo, $elements); + } + } diff --git a/vendor/doctrine/collections/docs/en/expression-builder.rst b/vendor/doctrine/collections/docs/en/expression-builder.rst new file mode 100644 index 000000000..ad4055b54 --- /dev/null +++ b/vendor/doctrine/collections/docs/en/expression-builder.rst @@ -0,0 +1,173 @@ +Expression Builder +================== + +The Expression Builder is a convenient fluent interface for +building expressions to be used with the ``Doctrine\Common\Collections\Criteria`` +class: + +.. code-block:: php + $expressionBuilder = Criteria::expr(); + + $criteria = new Criteria(); + $criteria->where($expressionBuilder->eq('name', 'jwage')); + $criteria->orWhere($expressionBuilder->eq('name', 'romanb')); + + $collection->matching($criteria); + +The ``ExpressionBuilder`` has the following API: + +andX +---- + +.. code-block:: php + $expressionBuilder = Criteria::expr(); + + $expression = $expressionBuilder->andX( + $expressionBuilder->eq('foo', 1), + $expressionBuilder->eq('bar', 1) + ); + + $collection->matching(new Criteria($expression)); + +orX +--- + +.. code-block:: php + $expressionBuilder = Criteria::expr(); + + $expression = $expressionBuilder->orX( + $expressionBuilder->eq('foo', 1), + $expressionBuilder->eq('bar', 1) + ); + + $collection->matching(new Criteria($expression)); + +eq +--- + +.. code-block:: php + $expressionBuilder = Criteria::expr(); + + $expression = $expressionBuilder->eq('foo', 1); + + $collection->matching(new Criteria($expression)); + +gt +--- + +.. code-block:: php + $expressionBuilder = Criteria::expr(); + + $expression = $expressionBuilder->gt('foo', 1); + + $collection->matching(new Criteria($expression)); + +lt +--- + +.. code-block:: php + $expressionBuilder = Criteria::expr(); + + $expression = $expressionBuilder->lt('foo', 1); + + $collection->matching(new Criteria($expression)); + +gte +--- + +.. code-block:: php + $expressionBuilder = Criteria::expr(); + + $expression = $expressionBuilder->gte('foo', 1); + + $collection->matching(new Criteria($expression)); + +lte +--- + +.. code-block:: php + $expressionBuilder = Criteria::expr(); + + $expression = $expressionBuilder->lte('foo', 1); + + $collection->matching(new Criteria($expression)); + +neq +--- + +.. code-block:: php + $expressionBuilder = Criteria::expr(); + + $expression = $expressionBuilder->neq('foo', 1); + + $collection->matching(new Criteria($expression)); + +isNull +------ + +.. code-block:: php + $expressionBuilder = Criteria::expr(); + + $expression = $expressionBuilder->isNull('foo'); + + $collection->matching(new Criteria($expression)); + +in +--- + +.. code-block:: php + $expressionBuilder = Criteria::expr(); + + $expression = $expressionBuilder->in('foo', ['value1', 'value2']); + + $collection->matching(new Criteria($expression)); + +notIn +----- + +.. code-block:: php + $expressionBuilder = Criteria::expr(); + + $expression = $expressionBuilder->notIn('foo', ['value1', 'value2']); + + $collection->matching(new Criteria($expression)); + +contains +-------- + +.. code-block:: php + $expressionBuilder = Criteria::expr(); + + $expression = $expressionBuilder->contains('foo', 'value1'); + + $collection->matching(new Criteria($expression)); + +memberOf +-------- + +.. code-block:: php + $expressionBuilder = Criteria::expr(); + + $expression = $expressionBuilder->memberOf('foo', ['value1', 'value2']); + + $collection->matching(new Criteria($expression)); + +startsWith +---------- + +.. code-block:: php + $expressionBuilder = Criteria::expr(); + + $expression = $expressionBuilder->startsWith('foo', 'hello'); + + $collection->matching(new Criteria($expression)); + +endsWith +-------- + +.. code-block:: php + $expressionBuilder = Criteria::expr(); + + $expression = $expressionBuilder->endsWith('foo', 'world'); + + $collection->matching(new Criteria($expression)); diff --git a/vendor/doctrine/collections/docs/en/expressions.rst b/vendor/doctrine/collections/docs/en/expressions.rst new file mode 100644 index 000000000..db943b246 --- /dev/null +++ b/vendor/doctrine/collections/docs/en/expressions.rst @@ -0,0 +1,102 @@ +Expressions +=========== + +The ``Doctrine\Common\Collections\Expr\Comparison`` class +can be used to create expressions to be used with the +``Doctrine\Common\Collections\Criteria`` class. It has the +following operator constants: + +- ``Comparison::EQ`` +- ``Comparison::NEQ`` +- ``Comparison::LT`` +- ``Comparison::LTE`` +- ``Comparison::GT`` +- ``Comparison::GTE`` +- ``Comparison::IS`` +- ``Comparison::IN`` +- ``Comparison::NIN`` +- ``Comparison::CONTAINS`` +- ``Comparison::MEMBER_OF`` +- ``Comparison::STARTS_WITH`` +- ``Comparison::ENDS_WITH`` + +The ``Doctrine\Common\Collections\Criteria`` class has the following +API to be used with expressions: + +where +----- + +Sets the where expression to evaluate when this Criteria is searched for. + +.. code-block:: php + $expr = new Comparison('key', Comparison::EQ, 'value'); + + $criteria->where($expr); + +andWhere +-------- + +Appends the where expression to evaluate when this Criteria is searched for +using an AND with previous expression. + +.. code-block:: php + $expr = new Comparison('key', Comparison::EQ, 'value'); + + $criteria->andWhere($expr); + +orWhere +------- + +Appends the where expression to evaluate when this Criteria is searched for +using an OR with previous expression. + +.. code-block:: php + $expr1 = new Comparison('key', Comparison::EQ, 'value1'); + $expr2 = new Comparison('key', Comparison::EQ, 'value2'); + + $criteria->where($expr1); + $criteria->orWhere($expr2); + +orderBy +------- + +Sets the ordering of the result of this Criteria. + +.. code-block:: php + $criteria->orderBy(['name' => Criteria::ASC]); + +setFirstResult +-------------- + +Set the number of first result that this Criteria should return. + +.. code-block:: php + $criteria->setFirstResult(0); + +getFirstResult +-------------- + +Gets the current first result option of this Criteria. + +.. code-block:: php + $criteria->setFirstResult(10); + + echo $criteria->getFirstResult(); // 10 + +setMaxResults +------------- + +Sets the max results that this Criteria should return. + +.. code-block:: php + $criteria->setMaxResults(20); + +getMaxResults +------------- + +Gets the current max results option of this Criteria. + +.. code-block:: php + $criteria->setMaxResults(20); + + echo $criteria->getMaxResults(); // 20 diff --git a/vendor/doctrine/collections/docs/en/index.rst b/vendor/doctrine/collections/docs/en/index.rst new file mode 100644 index 000000000..447f76e8a --- /dev/null +++ b/vendor/doctrine/collections/docs/en/index.rst @@ -0,0 +1,327 @@ +Introduction +============ + +Doctrine Collections is a library that contains classes for working with +arrays of data. Here is an example using the simple +``Doctrine\Common\Collections\ArrayCollection`` class: + +.. code-block:: php + filter(function($count) { + return $count > 1; + }); // [2, 3] + +Collection Methods +================== + +Doctrine Collections provides an interface named ``Doctrine\Common\Collections\Collection`` +that resembles the nature of a regular PHP array. That is, +it is essentially an **ordered map** that can also be used +like a list. + +A Collection has an internal iterator just like a PHP array. In addition, +a Collection can be iterated with external iterators, which is preferable. +To use an external iterator simply use the foreach language construct to +iterate over the collection, which calls ``getIterator()`` internally, or +explicitly retrieve an iterator though ``getIterator()`` which can then be +used to iterate over the collection. You can not rely on the internal iterator +of the collection being at a certain position unless you explicitly positioned it before. + +The methods available on the interface are: + +add +--- + +Adds an element at the end of the collection. + +.. code-block:: php + $collection->add('test'); + +clear +----- + +Clears the collection, removing all elements. + +.. code-block:: php + $collection->clear(); + +contains +-------- + +Checks whether an element is contained in the collection. This is an O(n) operation, where n is the size of the collection. + +.. code-block:: php + $collection = new Collection(['test']); + + $contains = $collection->contains('test'); // true + +containsKey +----------- + +Checks whether the collection contains an element with the specified key/index. + +.. code-block:: php + $collection = new Collection(['test' => true]); + + $contains = $collection->containsKey('test'); // true + +current +------- + +Gets the element of the collection at the current iterator position. + +.. code-block:: php + $collection = new Collection(['first', 'second', 'third']); + + $current = $collection->current(); // first + +get +--- + +Gets the element at the specified key/index. + +.. code-block:: php + $collection = new Collection([ + 'key' => 'value', + ]); + + $value = $collection->get('key'); // value + +getKeys +------- + +Gets all keys/indices of the collection. + +.. code-block:: php + $collection = new Collection(['a', 'b', 'c']); + + $keys = $collection->getKeys(); // [0, 1, 2] + +getValues +--------- + +Gets all values of the collection. + +.. code-block:: php + $collection = new Collection([ + 'key1' => 'value1', + 'key2' => 'value2', + 'key3' => 'value3', + ]); + + $values = $collection->getValues(); // ['value1', 'value2', 'value3'] + +isEmpty +------- + +Checks whether the collection is empty (contains no elements). + +.. code-block:: php + $collection = new Collection(['a', 'b', 'c']); + + $isEmpty = $collection->isEmpty(); // false + +first +----- + +Sets the internal iterator to the first element in the collection and returns this element. + +.. code-block:: php + $collection = new Collection(['first', 'second', 'third']); + + $first = $collection->first(); // first + +exists +------ + +Tests for the existence of an element that satisfies the given predicate. + +.. code-block:: php + $collection = new Collection(['first', 'second', 'third']); + + $exists = $collection->exists(function($key, $value) { + return $value === 'first'; + }); // true + +filter +------ + +Returns all the elements of this collection that satisfy the predicate. The order of the elements is preserved. + +.. code-block:: php + $collection = new ArrayCollection([1, 2, 3]); + + $filteredCollection = $collection->filter(function($count) { + return $count > 1; + }); // [2, 3] + +forAll +------ + +Tests whether the given predicate holds for all elements of this collection. + +.. code-block:: php + $collection = new ArrayCollection([1, 2, 3]); + + $forAll = $collection->forAll(function($key, $value) { + return $value > 1; + }); // false + +indexOf +------- + +Gets the index/key of a given element. The comparison of two elements is strict, that means not only the value but also the type must match. For objects this means reference equality. + +.. code-block:: php + $collection = new ArrayCollection([1, 2, 3]); + + $indexOf = $collection->indexOf(3); // 2 + +key +--- + +Gets the key/index of the element at the current iterator position. + +.. code-block:: php + $collection = new ArrayCollection([1, 2, 3]); + + $collection->next(); + + $key = $collection->key(); // 1 + +last +---- + +Sets the internal iterator to the last element in the collection and returns this element. + +.. code-block:: php + $collection = new ArrayCollection([1, 2, 3]); + + $last = $collection->last(); // 3 + +map +--- + +Applies the given function to each element in the collection and returns a new collection with the elements returned by the function. + +.. code-block:: php + $collection = new ArrayCollection([1, 2, 3]); + + $mappedCollection = $collection->map(function($value) { + return $value + 1; + }); // [2, 3, 4] + +next +---- + +Moves the internal iterator position to the next element and returns this element. + +.. code-block:: php + $collection = new ArrayCollection([1, 2, 3]); + + $next = $collection->next(); // 2 + +partition +--------- + +Partitions this collection in two collections according to a predicate. Keys are preserved in the resulting collections. + +.. code-block:: php + $collection = new ArrayCollection([1, 2, 3]); + + $mappedCollection = $collection->partition(function($key, $value) { + return $value > 1 + }); // [[2, 3], [1]] + +remove +------ + +Removes the element at the specified index from the collection. + +.. code-block:: php + $collection = new ArrayCollection([1, 2, 3]); + + $collection->remove(0); // [2, 3] + +removeElement +------------- + +Removes the specified element from the collection, if it is found. + +.. code-block:: php + $collection = new ArrayCollection([1, 2, 3]); + + $collection->removeElement(3); // [1, 2] + +set +--- + +Sets an element in the collection at the specified key/index. + +.. code-block:: php + $collection = new ArrayCollection(); + + $collection->set('name', 'jwage'); + +slice +----- + +Extracts a slice of $length elements starting at position $offset from the Collection. If $length is null it returns all elements from $offset to the end of the Collection. Keys have to be preserved by this method. Calling this method will only return the selected slice and NOT change the elements contained in the collection slice is called on. + +.. code-block:: php + $collection = new ArrayCollection([0, 1, 2, 3, 4, 5]); + + $slice = $collection->slice(1, 2); // [1, 2] + +toArray +------- + +Gets a native PHP array representation of the collection. + +.. code-block:: php + $collection = new ArrayCollection([0, 1, 2, 3, 4, 5]); + + $array = $collection->toArray(); // [0, 1, 2, 3, 4, 5] + +Selectable Methods +================== + +Some Doctrine Collections, like ``Doctrine\Common\Collections\ArrayCollection``, +implement an interface named ``Doctrine\Common\Collections\Selectable`` +that offers the usage of a powerful expressions API, where conditions +can be applied to a collection to get a result with matching elements +only. + +matching +-------- + +Selects all elements from a selectable that match the expression and +returns a new collection containing these elements. + +.. code-block:: php + use Doctrine\Common\Collections\Criteria; + use Doctrine\Common\Collections\Expr\Comparison; + + $collection = new ArrayCollection([ + [ + 'name' => 'jwage', + ], + [ + 'name' => 'romanb', + ], + ]); + + $expr = new Comparison('name', '=', 'jwage'); + + $criteria = new Criteria(); + + $criteria->where($expr); + + $matched = $collection->matching($criteria); // ['jwage'] + +You can read more about expressions :ref:`here `. diff --git a/vendor/doctrine/collections/docs/en/lazy-collections.rst b/vendor/doctrine/collections/docs/en/lazy-collections.rst new file mode 100644 index 000000000..b9cafb60b --- /dev/null +++ b/vendor/doctrine/collections/docs/en/lazy-collections.rst @@ -0,0 +1,26 @@ +Lazy Collections +================ + +To create a lazy collection you can extend the +``Doctrine\Common\Collections\AbstractLazyCollection`` class +and define the ``doInitialize`` method. Here is an example where +we lazily query the database for a collection of user records: + +.. code-block:: php + use Doctrine\DBAL\Connection; + + class UsersLazyCollection extends AbstractLazyCollection + { + /** @var Connection */ + private $connection; + + public function __construct(Connection $connection) + { + $this->connection = $connection; + } + + protected function doInitialize() : void + { + $this->collection = $this->connection->fetchAll('SELECT * FROM users'); + } + } diff --git a/vendor/doctrine/collections/docs/en/sidebar.rst b/vendor/doctrine/collections/docs/en/sidebar.rst new file mode 100644 index 000000000..69279a0bb --- /dev/null +++ b/vendor/doctrine/collections/docs/en/sidebar.rst @@ -0,0 +1,8 @@ +.. toctree:: + :depth: 3 + + index + expressions + expression-builder + derived-collections + lazy-collections diff --git a/vendor/doctrine/collections/lib/Doctrine/Common/Collections/AbstractLazyCollection.php b/vendor/doctrine/collections/lib/Doctrine/Common/Collections/AbstractLazyCollection.php new file mode 100644 index 000000000..28976f16e --- /dev/null +++ b/vendor/doctrine/collections/lib/Doctrine/Common/Collections/AbstractLazyCollection.php @@ -0,0 +1,369 @@ + + */ +abstract class AbstractLazyCollection implements Collection +{ + /** + * The backed collection to use + * + * @psalm-var Collection + * @var Collection + */ + protected $collection; + + /** @var bool */ + protected $initialized = false; + + /** + * {@inheritDoc} + */ + public function count() + { + $this->initialize(); + + return $this->collection->count(); + } + + /** + * {@inheritDoc} + */ + public function add($element) + { + $this->initialize(); + + return $this->collection->add($element); + } + + /** + * {@inheritDoc} + */ + public function clear() + { + $this->initialize(); + $this->collection->clear(); + } + + /** + * {@inheritDoc} + */ + public function contains($element) + { + $this->initialize(); + + return $this->collection->contains($element); + } + + /** + * {@inheritDoc} + */ + public function isEmpty() + { + $this->initialize(); + + return $this->collection->isEmpty(); + } + + /** + * {@inheritDoc} + */ + public function remove($key) + { + $this->initialize(); + + return $this->collection->remove($key); + } + + /** + * {@inheritDoc} + */ + public function removeElement($element) + { + $this->initialize(); + + return $this->collection->removeElement($element); + } + + /** + * {@inheritDoc} + */ + public function containsKey($key) + { + $this->initialize(); + + return $this->collection->containsKey($key); + } + + /** + * {@inheritDoc} + */ + public function get($key) + { + $this->initialize(); + + return $this->collection->get($key); + } + + /** + * {@inheritDoc} + */ + public function getKeys() + { + $this->initialize(); + + return $this->collection->getKeys(); + } + + /** + * {@inheritDoc} + */ + public function getValues() + { + $this->initialize(); + + return $this->collection->getValues(); + } + + /** + * {@inheritDoc} + */ + public function set($key, $value) + { + $this->initialize(); + $this->collection->set($key, $value); + } + + /** + * {@inheritDoc} + */ + public function toArray() + { + $this->initialize(); + + return $this->collection->toArray(); + } + + /** + * {@inheritDoc} + */ + public function first() + { + $this->initialize(); + + return $this->collection->first(); + } + + /** + * {@inheritDoc} + */ + public function last() + { + $this->initialize(); + + return $this->collection->last(); + } + + /** + * {@inheritDoc} + */ + public function key() + { + $this->initialize(); + + return $this->collection->key(); + } + + /** + * {@inheritDoc} + */ + public function current() + { + $this->initialize(); + + return $this->collection->current(); + } + + /** + * {@inheritDoc} + */ + public function next() + { + $this->initialize(); + + return $this->collection->next(); + } + + /** + * {@inheritDoc} + */ + public function exists(Closure $p) + { + $this->initialize(); + + return $this->collection->exists($p); + } + + /** + * {@inheritDoc} + */ + public function filter(Closure $p) + { + $this->initialize(); + + return $this->collection->filter($p); + } + + /** + * {@inheritDoc} + */ + public function forAll(Closure $p) + { + $this->initialize(); + + return $this->collection->forAll($p); + } + + /** + * {@inheritDoc} + */ + public function map(Closure $func) + { + $this->initialize(); + + return $this->collection->map($func); + } + + /** + * {@inheritDoc} + */ + public function partition(Closure $p) + { + $this->initialize(); + + return $this->collection->partition($p); + } + + /** + * {@inheritDoc} + */ + public function indexOf($element) + { + $this->initialize(); + + return $this->collection->indexOf($element); + } + + /** + * {@inheritDoc} + */ + public function slice($offset, $length = null) + { + $this->initialize(); + + return $this->collection->slice($offset, $length); + } + + /** + * {@inheritDoc} + */ + public function getIterator() + { + $this->initialize(); + + return $this->collection->getIterator(); + } + + /** + * {@inheritDoc} + * + * @psalm-param TKey $offset + */ + public function offsetExists($offset) + { + $this->initialize(); + + return $this->collection->offsetExists($offset); + } + + /** + * {@inheritDoc} + * + * @param int|string $offset + * + * @return mixed + * + * @psalm-param TKey $offset + */ + public function offsetGet($offset) + { + $this->initialize(); + + return $this->collection->offsetGet($offset); + } + + /** + * {@inheritDoc} + * + * @param mixed $value + * + * @psalm-param TKey $offset + */ + public function offsetSet($offset, $value) + { + $this->initialize(); + $this->collection->offsetSet($offset, $value); + } + + /** + * {@inheritDoc} + * + * @psalm-param TKey $offset + */ + public function offsetUnset($offset) + { + $this->initialize(); + $this->collection->offsetUnset($offset); + } + + /** + * Is the lazy collection already initialized? + * + * @return bool + */ + public function isInitialized() + { + return $this->initialized; + } + + /** + * Initialize the collection + * + * @return void + */ + protected function initialize() + { + if ($this->initialized) { + return; + } + + $this->doInitialize(); + $this->initialized = true; + } + + /** + * Do the initialization logic + * + * @return void + */ + abstract protected function doInitialize(); +} diff --git a/vendor/doctrine/collections/lib/Doctrine/Common/Collections/ArrayCollection.php b/vendor/doctrine/collections/lib/Doctrine/Common/Collections/ArrayCollection.php new file mode 100644 index 000000000..e65a8fa80 --- /dev/null +++ b/vendor/doctrine/collections/lib/Doctrine/Common/Collections/ArrayCollection.php @@ -0,0 +1,441 @@ + + * @template-implements Selectable + */ +class ArrayCollection implements Collection, Selectable +{ + /** + * An array containing the entries of this collection. + * + * @psalm-var array + * @var array + */ + private $elements; + + /** + * Initializes a new ArrayCollection. + * + * @param array $elements + * + * @psalm-param array $elements + */ + public function __construct(array $elements = []) + { + $this->elements = $elements; + } + + /** + * {@inheritDoc} + */ + public function toArray() + { + return $this->elements; + } + + /** + * {@inheritDoc} + */ + public function first() + { + return reset($this->elements); + } + + /** + * Creates a new instance from the specified elements. + * + * This method is provided for derived classes to specify how a new + * instance should be created when constructor semantics have changed. + * + * @param array $elements Elements. + * + * @return static + * + * @psalm-param array $elements + * @psalm-return static + */ + protected function createFrom(array $elements) + { + return new static($elements); + } + + /** + * {@inheritDoc} + */ + public function last() + { + return end($this->elements); + } + + /** + * {@inheritDoc} + */ + public function key() + { + return key($this->elements); + } + + /** + * {@inheritDoc} + */ + public function next() + { + return next($this->elements); + } + + /** + * {@inheritDoc} + */ + public function current() + { + return current($this->elements); + } + + /** + * {@inheritDoc} + */ + public function remove($key) + { + if (! isset($this->elements[$key]) && ! array_key_exists($key, $this->elements)) { + return null; + } + + $removed = $this->elements[$key]; + unset($this->elements[$key]); + + return $removed; + } + + /** + * {@inheritDoc} + */ + public function removeElement($element) + { + $key = array_search($element, $this->elements, true); + + if ($key === false) { + return false; + } + + unset($this->elements[$key]); + + return true; + } + + /** + * Required by interface ArrayAccess. + * + * {@inheritDoc} + * + * @psalm-param TKey $offset + */ + public function offsetExists($offset) + { + return $this->containsKey($offset); + } + + /** + * Required by interface ArrayAccess. + * + * {@inheritDoc} + * + * @psalm-param TKey $offset + */ + public function offsetGet($offset) + { + return $this->get($offset); + } + + /** + * Required by interface ArrayAccess. + * + * {@inheritDoc} + */ + public function offsetSet($offset, $value) + { + if (! isset($offset)) { + $this->add($value); + + return; + } + + $this->set($offset, $value); + } + + /** + * Required by interface ArrayAccess. + * + * {@inheritDoc} + * + * @psalm-param TKey $offset + */ + public function offsetUnset($offset) + { + $this->remove($offset); + } + + /** + * {@inheritDoc} + */ + public function containsKey($key) + { + return isset($this->elements[$key]) || array_key_exists($key, $this->elements); + } + + /** + * {@inheritDoc} + */ + public function contains($element) + { + return in_array($element, $this->elements, true); + } + + /** + * {@inheritDoc} + */ + public function exists(Closure $p) + { + foreach ($this->elements as $key => $element) { + if ($p($key, $element)) { + return true; + } + } + + return false; + } + + /** + * {@inheritDoc} + */ + public function indexOf($element) + { + return array_search($element, $this->elements, true); + } + + /** + * {@inheritDoc} + */ + public function get($key) + { + return $this->elements[$key] ?? null; + } + + /** + * {@inheritDoc} + */ + public function getKeys() + { + return array_keys($this->elements); + } + + /** + * {@inheritDoc} + */ + public function getValues() + { + return array_values($this->elements); + } + + /** + * {@inheritDoc} + */ + public function count() + { + return count($this->elements); + } + + /** + * {@inheritDoc} + */ + public function set($key, $value) + { + $this->elements[$key] = $value; + } + + /** + * {@inheritDoc} + * + * @psalm-suppress InvalidPropertyAssignmentValue + * + * This breaks assumptions about the template type, but it would + * be a backwards-incompatible change to remove this method + */ + public function add($element) + { + $this->elements[] = $element; + + return true; + } + + /** + * {@inheritDoc} + */ + public function isEmpty() + { + return empty($this->elements); + } + + /** + * Required by interface IteratorAggregate. + * + * {@inheritDoc} + */ + public function getIterator() + { + return new ArrayIterator($this->elements); + } + + /** + * {@inheritDoc} + * + * @return static + * + * @psalm-template U + * @psalm-param Closure(T=):U $func + * @psalm-return static + */ + public function map(Closure $func) + { + return $this->createFrom(array_map($func, $this->elements)); + } + + /** + * {@inheritDoc} + * + * @return static + * + * @psalm-return static + */ + public function filter(Closure $p) + { + return $this->createFrom(array_filter($this->elements, $p, ARRAY_FILTER_USE_BOTH)); + } + + /** + * {@inheritDoc} + */ + public function forAll(Closure $p) + { + foreach ($this->elements as $key => $element) { + if (! $p($key, $element)) { + return false; + } + } + + return true; + } + + /** + * {@inheritDoc} + */ + public function partition(Closure $p) + { + $matches = $noMatches = []; + + foreach ($this->elements as $key => $element) { + if ($p($key, $element)) { + $matches[$key] = $element; + } else { + $noMatches[$key] = $element; + } + } + + return [$this->createFrom($matches), $this->createFrom($noMatches)]; + } + + /** + * Returns a string representation of this object. + * + * @return string + */ + public function __toString() + { + return self::class . '@' . spl_object_hash($this); + } + + /** + * {@inheritDoc} + */ + public function clear() + { + $this->elements = []; + } + + /** + * {@inheritDoc} + */ + public function slice($offset, $length = null) + { + return array_slice($this->elements, $offset, $length, true); + } + + /** + * {@inheritDoc} + */ + public function matching(Criteria $criteria) + { + $expr = $criteria->getWhereExpression(); + $filtered = $this->elements; + + if ($expr) { + $visitor = new ClosureExpressionVisitor(); + $filter = $visitor->dispatch($expr); + $filtered = array_filter($filtered, $filter); + } + + $orderings = $criteria->getOrderings(); + + if ($orderings) { + $next = null; + foreach (array_reverse($orderings) as $field => $ordering) { + $next = ClosureExpressionVisitor::sortByField($field, $ordering === Criteria::DESC ? -1 : 1, $next); + } + + uasort($filtered, $next); + } + + $offset = $criteria->getFirstResult(); + $length = $criteria->getMaxResults(); + + if ($offset || $length) { + $filtered = array_slice($filtered, (int) $offset, $length); + } + + return $this->createFrom($filtered); + } +} diff --git a/vendor/doctrine/collections/lib/Doctrine/Common/Collections/Collection.php b/vendor/doctrine/collections/lib/Doctrine/Common/Collections/Collection.php new file mode 100644 index 000000000..ab1e3f890 --- /dev/null +++ b/vendor/doctrine/collections/lib/Doctrine/Common/Collections/Collection.php @@ -0,0 +1,297 @@ +ordered map that can also be used + * like a list. + * + * A Collection has an internal iterator just like a PHP array. In addition, + * a Collection can be iterated with external iterators, which is preferable. + * To use an external iterator simply use the foreach language construct to + * iterate over the collection (which calls {@link getIterator()} internally) or + * explicitly retrieve an iterator though {@link getIterator()} which can then be + * used to iterate over the collection. + * You can not rely on the internal iterator of the collection being at a certain + * position unless you explicitly positioned it before. Prefer iteration with + * external iterators. + * + * @phpstan-template TKey + * @psalm-template TKey of array-key + * @psalm-template T + * @template-extends IteratorAggregate + * @template-extends ArrayAccess + */ +interface Collection extends Countable, IteratorAggregate, ArrayAccess +{ + /** + * Adds an element at the end of the collection. + * + * @param mixed $element The element to add. + * + * @return true Always TRUE. + * + * @psalm-param T $element + */ + public function add($element); + + /** + * Clears the collection, removing all elements. + * + * @return void + */ + public function clear(); + + /** + * Checks whether an element is contained in the collection. + * This is an O(n) operation, where n is the size of the collection. + * + * @param mixed $element The element to search for. + * + * @return bool TRUE if the collection contains the element, FALSE otherwise. + * + * @psalm-param T $element + */ + public function contains($element); + + /** + * Checks whether the collection is empty (contains no elements). + * + * @return bool TRUE if the collection is empty, FALSE otherwise. + */ + public function isEmpty(); + + /** + * Removes the element at the specified index from the collection. + * + * @param string|int $key The key/index of the element to remove. + * + * @return mixed The removed element or NULL, if the collection did not contain the element. + * + * @psalm-param TKey $key + * @psalm-return T|null + */ + public function remove($key); + + /** + * Removes the specified element from the collection, if it is found. + * + * @param mixed $element The element to remove. + * + * @return bool TRUE if this collection contained the specified element, FALSE otherwise. + * + * @psalm-param T $element + */ + public function removeElement($element); + + /** + * Checks whether the collection contains an element with the specified key/index. + * + * @param string|int $key The key/index to check for. + * + * @return bool TRUE if the collection contains an element with the specified key/index, + * FALSE otherwise. + * + * @psalm-param TKey $key + */ + public function containsKey($key); + + /** + * Gets the element at the specified key/index. + * + * @param string|int $key The key/index of the element to retrieve. + * + * @return mixed + * + * @psalm-param TKey $key + * @psalm-return T|null + */ + public function get($key); + + /** + * Gets all keys/indices of the collection. + * + * @return int[]|string[] The keys/indices of the collection, in the order of the corresponding + * elements in the collection. + * + * @psalm-return TKey[] + */ + public function getKeys(); + + /** + * Gets all values of the collection. + * + * @return array The values of all elements in the collection, in the order they + * appear in the collection. + * + * @psalm-return T[] + */ + public function getValues(); + + /** + * Sets an element in the collection at the specified key/index. + * + * @param string|int $key The key/index of the element to set. + * @param mixed $value The element to set. + * + * @return void + * + * @psalm-param TKey $key + * @psalm-param T $value + */ + public function set($key, $value); + + /** + * Gets a native PHP array representation of the collection. + * + * @return array + * + * @psalm-return array + */ + public function toArray(); + + /** + * Sets the internal iterator to the first element in the collection and returns this element. + * + * @return mixed + * + * @psalm-return T|false + */ + public function first(); + + /** + * Sets the internal iterator to the last element in the collection and returns this element. + * + * @return mixed + * + * @psalm-return T|false + */ + public function last(); + + /** + * Gets the key/index of the element at the current iterator position. + * + * @return int|string|null + * + * @psalm-return TKey|null + */ + public function key(); + + /** + * Gets the element of the collection at the current iterator position. + * + * @return mixed + * + * @psalm-return T|false + */ + public function current(); + + /** + * Moves the internal iterator position to the next element and returns this element. + * + * @return mixed + * + * @psalm-return T|false + */ + public function next(); + + /** + * Tests for the existence of an element that satisfies the given predicate. + * + * @param Closure $p The predicate. + * + * @return bool TRUE if the predicate is TRUE for at least one element, FALSE otherwise. + * + * @psalm-param Closure(TKey=, T=):bool $p + */ + public function exists(Closure $p); + + /** + * Returns all the elements of this collection that satisfy the predicate p. + * The order of the elements is preserved. + * + * @param Closure $p The predicate used for filtering. + * + * @return Collection A collection with the results of the filter operation. + * + * @psalm-param Closure(T=):bool $p + * @psalm-return Collection + */ + public function filter(Closure $p); + + /** + * Tests whether the given predicate p holds for all elements of this collection. + * + * @param Closure $p The predicate. + * + * @return bool TRUE, if the predicate yields TRUE for all elements, FALSE otherwise. + * + * @psalm-param Closure(TKey=, T=):bool $p + */ + public function forAll(Closure $p); + + /** + * Applies the given function to each element in the collection and returns + * a new collection with the elements returned by the function. + * + * @return Collection + * + * @psalm-template U + * @psalm-param Closure(T=):U $func + * @psalm-return Collection + */ + public function map(Closure $func); + + /** + * Partitions this collection in two collections according to a predicate. + * Keys are preserved in the resulting collections. + * + * @param Closure $p The predicate on which to partition. + * + * @return Collection[] An array with two elements. The first element contains the collection + * of elements where the predicate returned TRUE, the second element + * contains the collection of elements where the predicate returned FALSE. + * + * @psalm-param Closure(TKey=, T=):bool $p + * @psalm-return array{0: Collection, 1: Collection} + */ + public function partition(Closure $p); + + /** + * Gets the index/key of a given element. The comparison of two elements is strict, + * that means not only the value but also the type must match. + * For objects this means reference equality. + * + * @param mixed $element The element to search for. + * + * @return int|string|bool The key/index of the element or FALSE if the element was not found. + * + * @psalm-param T $element + * @psalm-return TKey|false + */ + public function indexOf($element); + + /** + * Extracts a slice of $length elements starting at position $offset from the Collection. + * + * If $length is null it returns all elements from $offset to the end of the Collection. + * Keys have to be preserved by this method. Calling this method will only return the + * selected slice and NOT change the elements contained in the collection slice is called on. + * + * @param int $offset The offset to start from. + * @param int|null $length The maximum number of elements to return, or null for no limit. + * + * @return array + * + * @psalm-return array + */ + public function slice($offset, $length = null); +} diff --git a/vendor/doctrine/collections/lib/Doctrine/Common/Collections/Criteria.php b/vendor/doctrine/collections/lib/Doctrine/Common/Collections/Criteria.php new file mode 100644 index 000000000..6ddaf5e40 --- /dev/null +++ b/vendor/doctrine/collections/lib/Doctrine/Common/Collections/Criteria.php @@ -0,0 +1,222 @@ +expression = $expression; + + $this->setFirstResult($firstResult); + $this->setMaxResults($maxResults); + + if ($orderings === null) { + return; + } + + $this->orderBy($orderings); + } + + /** + * Sets the where expression to evaluate when this Criteria is searched for. + * + * @return Criteria + */ + public function where(Expression $expression) + { + $this->expression = $expression; + + return $this; + } + + /** + * Appends the where expression to evaluate when this Criteria is searched for + * using an AND with previous expression. + * + * @return Criteria + */ + public function andWhere(Expression $expression) + { + if ($this->expression === null) { + return $this->where($expression); + } + + $this->expression = new CompositeExpression( + CompositeExpression::TYPE_AND, + [$this->expression, $expression] + ); + + return $this; + } + + /** + * Appends the where expression to evaluate when this Criteria is searched for + * using an OR with previous expression. + * + * @return Criteria + */ + public function orWhere(Expression $expression) + { + if ($this->expression === null) { + return $this->where($expression); + } + + $this->expression = new CompositeExpression( + CompositeExpression::TYPE_OR, + [$this->expression, $expression] + ); + + return $this; + } + + /** + * Gets the expression attached to this Criteria. + * + * @return Expression|null + */ + public function getWhereExpression() + { + return $this->expression; + } + + /** + * Gets the current orderings of this Criteria. + * + * @return string[] + */ + public function getOrderings() + { + return $this->orderings; + } + + /** + * Sets the ordering of the result of this Criteria. + * + * Keys are field and values are the order, being either ASC or DESC. + * + * @see Criteria::ASC + * @see Criteria::DESC + * + * @param string[] $orderings + * + * @return Criteria + */ + public function orderBy(array $orderings) + { + $this->orderings = array_map( + static function (string $ordering) : string { + return strtoupper($ordering) === Criteria::ASC ? Criteria::ASC : Criteria::DESC; + }, + $orderings + ); + + return $this; + } + + /** + * Gets the current first result option of this Criteria. + * + * @return int|null + */ + public function getFirstResult() + { + return $this->firstResult; + } + + /** + * Set the number of first result that this Criteria should return. + * + * @param int|null $firstResult The value to set. + * + * @return Criteria + */ + public function setFirstResult($firstResult) + { + $this->firstResult = $firstResult === null ? null : (int) $firstResult; + + return $this; + } + + /** + * Gets maxResults. + * + * @return int|null + */ + public function getMaxResults() + { + return $this->maxResults; + } + + /** + * Sets maxResults. + * + * @param int|null $maxResults The value to set. + * + * @return Criteria + */ + public function setMaxResults($maxResults) + { + $this->maxResults = $maxResults === null ? null : (int) $maxResults; + + return $this; + } +} diff --git a/vendor/doctrine/collections/lib/Doctrine/Common/Collections/Expr/ClosureExpressionVisitor.php b/vendor/doctrine/collections/lib/Doctrine/Common/Collections/Expr/ClosureExpressionVisitor.php new file mode 100644 index 000000000..90ec79496 --- /dev/null +++ b/vendor/doctrine/collections/lib/Doctrine/Common/Collections/Expr/ClosureExpressionVisitor.php @@ -0,0 +1,262 @@ +$accessor(); + } + } + + if (preg_match('/^is[A-Z]+/', $field) === 1 && method_exists($object, $field)) { + return $object->$field(); + } + + // __call should be triggered for get. + $accessor = $accessors[0] . $field; + + if (method_exists($object, '__call')) { + return $object->$accessor(); + } + + if ($object instanceof ArrayAccess) { + return $object[$field]; + } + + if (isset($object->$field)) { + return $object->$field; + } + + // camelcase field name to support different variable naming conventions + $ccField = preg_replace_callback('/_(.?)/', static function ($matches) { + return strtoupper($matches[1]); + }, $field); + + foreach ($accessors as $accessor) { + $accessor .= $ccField; + + if (method_exists($object, $accessor)) { + return $object->$accessor(); + } + } + + return $object->$field; + } + + /** + * Helper for sorting arrays of objects based on multiple fields + orientations. + * + * @param string $name + * @param int $orientation + * + * @return Closure + */ + public static function sortByField($name, $orientation = 1, ?Closure $next = null) + { + if (! $next) { + $next = static function () : int { + return 0; + }; + } + + return static function ($a, $b) use ($name, $next, $orientation) : int { + $aValue = ClosureExpressionVisitor::getObjectFieldValue($a, $name); + + $bValue = ClosureExpressionVisitor::getObjectFieldValue($b, $name); + + if ($aValue === $bValue) { + return $next($a, $b); + } + + return ($aValue > $bValue ? 1 : -1) * $orientation; + }; + } + + /** + * {@inheritDoc} + */ + public function walkComparison(Comparison $comparison) + { + $field = $comparison->getField(); + $value = $comparison->getValue()->getValue(); // shortcut for walkValue() + + switch ($comparison->getOperator()) { + case Comparison::EQ: + return static function ($object) use ($field, $value) : bool { + return ClosureExpressionVisitor::getObjectFieldValue($object, $field) === $value; + }; + + case Comparison::NEQ: + return static function ($object) use ($field, $value) : bool { + return ClosureExpressionVisitor::getObjectFieldValue($object, $field) !== $value; + }; + + case Comparison::LT: + return static function ($object) use ($field, $value) : bool { + return ClosureExpressionVisitor::getObjectFieldValue($object, $field) < $value; + }; + + case Comparison::LTE: + return static function ($object) use ($field, $value) : bool { + return ClosureExpressionVisitor::getObjectFieldValue($object, $field) <= $value; + }; + + case Comparison::GT: + return static function ($object) use ($field, $value) : bool { + return ClosureExpressionVisitor::getObjectFieldValue($object, $field) > $value; + }; + + case Comparison::GTE: + return static function ($object) use ($field, $value) : bool { + return ClosureExpressionVisitor::getObjectFieldValue($object, $field) >= $value; + }; + + case Comparison::IN: + return static function ($object) use ($field, $value) : bool { + $fieldValue = ClosureExpressionVisitor::getObjectFieldValue($object, $field); + + return in_array($fieldValue, $value, is_scalar($fieldValue)); + }; + + case Comparison::NIN: + return static function ($object) use ($field, $value) : bool { + $fieldValue = ClosureExpressionVisitor::getObjectFieldValue($object, $field); + + return ! in_array($fieldValue, $value, is_scalar($fieldValue)); + }; + + case Comparison::CONTAINS: + return static function ($object) use ($field, $value) { + return strpos(ClosureExpressionVisitor::getObjectFieldValue($object, $field), $value) !== false; + }; + + case Comparison::MEMBER_OF: + return static function ($object) use ($field, $value) : bool { + $fieldValues = ClosureExpressionVisitor::getObjectFieldValue($object, $field); + + if (! is_array($fieldValues)) { + $fieldValues = iterator_to_array($fieldValues); + } + + return in_array($value, $fieldValues, true); + }; + + case Comparison::STARTS_WITH: + return static function ($object) use ($field, $value) : bool { + return strpos(ClosureExpressionVisitor::getObjectFieldValue($object, $field), $value) === 0; + }; + + case Comparison::ENDS_WITH: + return static function ($object) use ($field, $value) : bool { + return $value === substr(ClosureExpressionVisitor::getObjectFieldValue($object, $field), -strlen($value)); + }; + + default: + throw new RuntimeException('Unknown comparison operator: ' . $comparison->getOperator()); + } + } + + /** + * {@inheritDoc} + */ + public function walkValue(Value $value) + { + return $value->getValue(); + } + + /** + * {@inheritDoc} + */ + public function walkCompositeExpression(CompositeExpression $expr) + { + $expressionList = []; + + foreach ($expr->getExpressionList() as $child) { + $expressionList[] = $this->dispatch($child); + } + + switch ($expr->getType()) { + case CompositeExpression::TYPE_AND: + return $this->andExpressions($expressionList); + case CompositeExpression::TYPE_OR: + return $this->orExpressions($expressionList); + default: + throw new RuntimeException('Unknown composite ' . $expr->getType()); + } + } + + /** + * @param array $expressions + */ + private function andExpressions(array $expressions) : callable + { + return static function ($object) use ($expressions) : bool { + foreach ($expressions as $expression) { + if (! $expression($object)) { + return false; + } + } + + return true; + }; + } + + /** + * @param array $expressions + */ + private function orExpressions(array $expressions) : callable + { + return static function ($object) use ($expressions) : bool { + foreach ($expressions as $expression) { + if ($expression($object)) { + return true; + } + } + + return false; + }; + } +} diff --git a/vendor/doctrine/collections/lib/Doctrine/Common/Collections/Expr/Comparison.php b/vendor/doctrine/collections/lib/Doctrine/Common/Collections/Expr/Comparison.php new file mode 100644 index 000000000..13e193cb5 --- /dev/null +++ b/vendor/doctrine/collections/lib/Doctrine/Common/Collections/Expr/Comparison.php @@ -0,0 +1,80 @@ +'; + public const LT = '<'; + public const LTE = '<='; + public const GT = '>'; + public const GTE = '>='; + public const IS = '='; // no difference with EQ + public const IN = 'IN'; + public const NIN = 'NIN'; + public const CONTAINS = 'CONTAINS'; + public const MEMBER_OF = 'MEMBER_OF'; + public const STARTS_WITH = 'STARTS_WITH'; + public const ENDS_WITH = 'ENDS_WITH'; + + /** @var string */ + private $field; + + /** @var string */ + private $op; + + /** @var Value */ + private $value; + + /** + * @param string $field + * @param string $operator + * @param mixed $value + */ + public function __construct($field, $operator, $value) + { + if (! ($value instanceof Value)) { + $value = new Value($value); + } + + $this->field = $field; + $this->op = $operator; + $this->value = $value; + } + + /** + * @return string + */ + public function getField() + { + return $this->field; + } + + /** + * @return Value + */ + public function getValue() + { + return $this->value; + } + + /** + * @return string + */ + public function getOperator() + { + return $this->op; + } + + /** + * {@inheritDoc} + */ + public function visit(ExpressionVisitor $visitor) + { + return $visitor->walkComparison($this); + } +} diff --git a/vendor/doctrine/collections/lib/Doctrine/Common/Collections/Expr/CompositeExpression.php b/vendor/doctrine/collections/lib/Doctrine/Common/Collections/Expr/CompositeExpression.php new file mode 100644 index 000000000..e1f7114de --- /dev/null +++ b/vendor/doctrine/collections/lib/Doctrine/Common/Collections/Expr/CompositeExpression.php @@ -0,0 +1,68 @@ +type = $type; + + foreach ($expressions as $expr) { + if ($expr instanceof Value) { + throw new RuntimeException('Values are not supported expressions as children of and/or expressions.'); + } + if (! ($expr instanceof Expression)) { + throw new RuntimeException('No expression given to CompositeExpression.'); + } + + $this->expressions[] = $expr; + } + } + + /** + * Returns the list of expressions nested in this composite. + * + * @return Expression[] + */ + public function getExpressionList() + { + return $this->expressions; + } + + /** + * @return string + */ + public function getType() + { + return $this->type; + } + + /** + * {@inheritDoc} + */ + public function visit(ExpressionVisitor $visitor) + { + return $visitor->walkCompositeExpression($this); + } +} diff --git a/vendor/doctrine/collections/lib/Doctrine/Common/Collections/Expr/Expression.php b/vendor/doctrine/collections/lib/Doctrine/Common/Collections/Expr/Expression.php new file mode 100644 index 000000000..f40d529b6 --- /dev/null +++ b/vendor/doctrine/collections/lib/Doctrine/Common/Collections/Expr/Expression.php @@ -0,0 +1,14 @@ +walkComparison($expr); + case $expr instanceof Value: + return $this->walkValue($expr); + case $expr instanceof CompositeExpression: + return $this->walkCompositeExpression($expr); + default: + throw new RuntimeException('Unknown Expression ' . get_class($expr)); + } + } +} diff --git a/vendor/doctrine/collections/lib/Doctrine/Common/Collections/Expr/Value.php b/vendor/doctrine/collections/lib/Doctrine/Common/Collections/Expr/Value.php new file mode 100644 index 000000000..0830286e1 --- /dev/null +++ b/vendor/doctrine/collections/lib/Doctrine/Common/Collections/Expr/Value.php @@ -0,0 +1,33 @@ +value = $value; + } + + /** + * @return mixed + */ + public function getValue() + { + return $this->value; + } + + /** + * {@inheritDoc} + */ + public function visit(ExpressionVisitor $visitor) + { + return $visitor->walkValue($this); + } +} diff --git a/vendor/doctrine/collections/lib/Doctrine/Common/Collections/ExpressionBuilder.php b/vendor/doctrine/collections/lib/Doctrine/Common/Collections/ExpressionBuilder.php new file mode 100644 index 000000000..4c0f5fdc8 --- /dev/null +++ b/vendor/doctrine/collections/lib/Doctrine/Common/Collections/ExpressionBuilder.php @@ -0,0 +1,180 @@ + + */ + public function matching(Criteria $criteria); +} diff --git a/vendor/doctrine/collections/psalm.xml.dist b/vendor/doctrine/collections/psalm.xml.dist new file mode 100644 index 000000000..8c5b066d5 --- /dev/null +++ b/vendor/doctrine/collections/psalm.xml.dist @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/doctrine/common/.doctrine-project.json b/vendor/doctrine/common/.doctrine-project.json new file mode 100644 index 000000000..d29ce6d4e --- /dev/null +++ b/vendor/doctrine/common/.doctrine-project.json @@ -0,0 +1,18 @@ +{ + "active": true, + "name": "Common", + "slug": "common", + "docsSlug": "doctrine-common", + "versions": [ + { + "name": "master", + "branchName": "master", + "slug": "latest" + }, + { + "name": "2.11", + "branchName": "2.11", + "current": true + } + ] +} diff --git a/vendor/doctrine/common/.github/FUNDING.yml b/vendor/doctrine/common/.github/FUNDING.yml new file mode 100644 index 000000000..af1c7e712 --- /dev/null +++ b/vendor/doctrine/common/.github/FUNDING.yml @@ -0,0 +1,3 @@ +patreon: phpdoctrine +tidelift: packagist/doctrine%2Fcommon +custom: https://www.doctrine-project.org/sponsorship.html diff --git a/vendor/doctrine/common/LICENSE b/vendor/doctrine/common/LICENSE new file mode 100644 index 000000000..8c38cc1bc --- /dev/null +++ b/vendor/doctrine/common/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2006-2015 Doctrine Project + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/doctrine/common/README.md b/vendor/doctrine/common/README.md new file mode 100644 index 000000000..5cffb1abc --- /dev/null +++ b/vendor/doctrine/common/README.md @@ -0,0 +1,11 @@ +# Doctrine Common + +[![Build Status](https://secure.travis-ci.org/doctrine/common.png)](https://travis-ci.org/doctrine/common) + +The Doctrine Common project is a library that provides extensions to core PHP functionality. + +## More resources: + +* [Website](https://www.doctrine-project.org/) +* [Documentation](https://www.doctrine-project.org/projects/doctrine-common/en/latest/) +* [Downloads](https://github.com/doctrine/common/releases) diff --git a/vendor/doctrine/common/UPGRADE_TO_2_1 b/vendor/doctrine/common/UPGRADE_TO_2_1 new file mode 100644 index 000000000..891a2e5c2 --- /dev/null +++ b/vendor/doctrine/common/UPGRADE_TO_2_1 @@ -0,0 +1,39 @@ +This document details all the possible changes that you should investigate when updating +your project from Doctrine Common 2.0.x to 2.1 + +## AnnotationReader changes + +The annotation reader was heavily refactored between 2.0 and 2.1-RC1. In theory the operation of the new reader should be backwards compatible, but it has to be setup differently to work that way: + + $reader = new \Doctrine\Common\Annotations\AnnotationReader(); + $reader->setDefaultAnnotationNamespace('Doctrine\ORM\Mapping\\'); + // new code necessary starting here + $reader->setIgnoreNotImportedAnnotations(true); + $reader->setEnableParsePhpImports(false); + $reader = new \Doctrine\Common\Annotations\CachedReader( + new \Doctrine\Common\Annotations\IndexedReader($reader), new ArrayCache() + ); + +## Annotation Base class or @Annotation + +Beginning after 2.1-RC2 you have to either extend ``Doctrine\Common\Annotations\Annotation`` or add @Annotation to your annotations class-level docblock, otherwise the class will simply be ignored. + +## Removed methods on AnnotationReader + +* AnnotationReader::setAutoloadAnnotations() +* AnnotationReader::getAutoloadAnnotations() +* AnnotationReader::isAutoloadAnnotations() + +## AnnotationRegistry + +Autoloading through the PHP autoloader is removed from the 2.1 AnnotationReader. Instead you have to use the global AnnotationRegistry for loading purposes: + + \Doctrine\Common\Annotations\AnnotationRegistry::registerFile($fileWithAnnotations); + \Doctrine\Common\Annotations\AnnotationRegistry::registerAutoloadNamespace($namespace, $dirs = null); + \Doctrine\Common\Annotations\AnnotationRegistry::registerAutoloadNamespaces($namespaces); + \Doctrine\Common\Annotations\AnnotationRegistry::registerLoader($callable); + +The $callable for registering a loader accepts a class as first and only parameter and must try to silently autoload it. On success true has to be returned. +The registerAutoloadNamespace function registers a PSR-0 compatible silent autoloader for all classes with the given namespace in the given directories. +If null is passed as directory the include path will be used. + diff --git a/vendor/doctrine/common/UPGRADE_TO_2_2 b/vendor/doctrine/common/UPGRADE_TO_2_2 new file mode 100644 index 000000000..1d93a131e --- /dev/null +++ b/vendor/doctrine/common/UPGRADE_TO_2_2 @@ -0,0 +1,61 @@ +This document details all the possible changes that you should investigate when +updating your project from Doctrine Common 2.1 to 2.2: + +## Annotation Changes + +- AnnotationReader::setIgnoreNotImportedAnnotations has been removed, you need to + add ignore annotation names which are supposed to be ignored via + AnnotationReader::addGlobalIgnoredName + +- AnnotationReader::setAutoloadAnnotations was deprecated by the AnnotationRegistry + in 2.1 and has been removed in 2.2 + +- AnnotationReader::setEnableParsePhpImports was added to ease transition to the new + annotation mechanism in 2.1 and is removed in 2.2 + +- AnnotationReader::isParsePhpImportsEnabled is removed (see above) + +- AnnotationReader::setDefaultAnnotationNamespace was deprecated in favor of explicit + configuration in 2.1 and will be removed in 2.2 (for isolated projects where you + have full-control over _all_ available annotations, we offer a dedicated reader + class ``SimpleAnnotationReader``) + +- AnnotationReader::setAnnotationCreationFunction was deprecated in 2.1 and will be + removed in 2.2. We only offer two creation mechanisms which cannot be changed + anymore to allow the same reader instance to work with all annotations regardless + of which library they are coming from. + +- AnnotationReader::setAnnotationNamespaceAlias was deprecated in 2.1 and will be + removed in 2.2 (see setDefaultAnnotationNamespace) + +- If you use a class as annotation which has not the @Annotation marker in it's + class block, we will now throw an exception instead of silently ignoring it. You + can however still achieve the previous behavior using the @IgnoreAnnotation, or + AnnotationReader::addGlobalIgnoredName (the exception message will contain detailed + instructions when you run into this problem). + +## Cache Changes + +- Renamed old AbstractCache to CacheProvider + +- Dropped the support to the following functions of all cache providers: + + - CacheProvider::deleteByWildcard + + - CacheProvider::deleteByRegEx + + - CacheProvider::deleteByPrefix + + - CacheProvider::deleteBySuffix + +- CacheProvider::deleteAll will not remove ALL entries, it will only mark them as invalid + +- CacheProvider::flushAll will remove ALL entries, namespaced or not + +- Added support to MemcachedCache + +- Added support to WincacheCache + +## ClassLoader Changes + +- ClassLoader::fileExistsInIncludePath() no longer exists. Use the native stream_resolve_include_path() PHP function \ No newline at end of file diff --git a/vendor/doctrine/common/composer.json b/vendor/doctrine/common/composer.json new file mode 100644 index 000000000..92aa5ec37 --- /dev/null +++ b/vendor/doctrine/common/composer.json @@ -0,0 +1,54 @@ +{ + "name": "doctrine/common", + "type": "library", + "description": "PHP Doctrine Common project is a library that provides additional functionality that other Doctrine projects depend on such as better reflection support, persistence interfaces, proxies, event system and much more.", + "keywords": [ + "php", + "common", + "doctrine" + ], + "homepage": "https://www.doctrine-project.org/projects/common.html", + "license": "MIT", + "authors": [ + {"name": "Guilherme Blanco", "email": "guilhermeblanco@gmail.com"}, + {"name": "Roman Borschel", "email": "roman@code-factory.org"}, + {"name": "Benjamin Eberlei", "email": "kontakt@beberlei.de"}, + {"name": "Jonathan Wage", "email": "jonwage@gmail.com"}, + {"name": "Johannes Schmitt", "email": "schmittjoh@gmail.com"}, + {"name": "Marco Pivetta", "email": "ocramius@gmail.com"} + ], + "require": { + "php": "^7.1 || ^8.0", + "doctrine/inflector": "^1.0", + "doctrine/cache": "^1.0", + "doctrine/collections": "^1.0", + "doctrine/lexer": "^1.0", + "doctrine/annotations": "^1.0", + "doctrine/event-manager": "^1.0", + "doctrine/reflection": "^1.0", + "doctrine/persistence": "^1.3.3" + }, + "require-dev": { + "phpstan/phpstan": "^0.11", + "phpstan/phpstan-phpunit": "^0.11", + "phpunit/phpunit": "^7.0", + "doctrine/coding-standard": "^1.0", + "squizlabs/php_codesniffer": "^3.0", + "symfony/phpunit-bridge": "^4.0.5" + }, + "autoload": { + "psr-4": { + "Doctrine\\Common\\": "lib/Doctrine/Common" + } + }, + "autoload-dev": { + "psr-4": { + "Doctrine\\Tests\\": "tests/Doctrine/Tests" + } + }, + "extra": { + "branch-alias": { + "dev-master": "2.11.x-dev" + } + } +} diff --git a/vendor/doctrine/common/docs/en/index.rst b/vendor/doctrine/common/docs/en/index.rst new file mode 100644 index 000000000..7550d8b97 --- /dev/null +++ b/vendor/doctrine/common/docs/en/index.rst @@ -0,0 +1,10 @@ +Common Documentation +==================== + +Welcome to the Doctrine Common Library documentation. + +.. toctree:: + :depth: 2 + :glob: + + * diff --git a/vendor/doctrine/common/docs/en/reference/class-loading.rst b/vendor/doctrine/common/docs/en/reference/class-loading.rst new file mode 100644 index 000000000..e193b460e --- /dev/null +++ b/vendor/doctrine/common/docs/en/reference/class-loading.rst @@ -0,0 +1,242 @@ +Class Loading +============= + +Class loading is an essential part of any PHP application that +makes heavy use of classes and interfaces. Unfortunately, a lot of +people and projects spend a lot of time and effort on custom and +specialized class loading strategies. It can quickly become a pain +to understand what is going on when using multiple libraries and/or +frameworks, each with its own way to do class loading. Class +loading should be simple and it is an ideal candidate for +convention over configuration. + +Overview +-------- + +The Doctrine Common ClassLoader implements a simple and efficient +approach to class loading that is easy to understand and use. The +implementation is based on the widely used and accepted convention +of mapping namespace and class names to a directory structure. This +approach is used for example by Symfony2, the Zend Framework and of +course, Doctrine. + +For example, the following class: + +.. code-block:: php + + register(); + $dbalLoader->register(); + $ormLoader->register(); + +Do not be afraid of using multiple class loaders. Due to the +efficient class loading design you will not incur much overhead +from using many class loaders. Take a look at the implementation of +``ClassLoader#loadClass`` to see how simple and efficient the class +loading is. The iteration over the installed class loaders happens +in C (with the exception of using ``ClassLoader::classExists``). + +A ClassLoader can be used in the following other variations, +however, these are rarely used/needed: + + +- If only the second argument is not supplied, the class loader + will be responsible for the namespace prefix given in the first + argument and it will rely on the PHP include_path. + +- If only the first argument is not supplied, the class loader + will be responsible for *all* classes and it will try to look up + *all* classes starting at the directory given as the second + argument. + +- If both arguments are not supplied, the class loader will be + responsible for *all* classes and it will rely on the PHP + include_path. + + +File Extension +-------------- + +By default, a ClassLoader uses the ``.php`` file extension for all +class files. You can change this behavior, for example to use a +ClassLoader to load classes from a library that uses the +".class.php" convention (but it must nevertheless adhere to the +directory structure convention!): + +.. code-block:: php + + setFileExtension('.class.php'); + $customLoader->register(); + +Namespace Separator +------------------- + +By default, a ClassLoader uses the ``\`` namespace separator. You +can change this behavior, for example to use a ClassLoader to load +legacy Zend Framework classes that still use the underscore "_" +separator: + +.. code-block:: php + + setNamespaceSeparator('_'); + $zend1Loader->register(); + +Failing Silently and class_exists +---------------------------------- + +A lot of class/autoloaders these days try to fail silently when a +class file is not found. For the most part this is necessary in +order to support using ``class_exists('ClassName', true)`` which is +supposed to return a boolean value but triggers autoloading. This +is a bad thing as it basically forces class loaders to fail +silently, which in turn requires costly file_exists or fopen calls +for each class being loaded, even though in at least 99% of the +cases this is not necessary (compare the number of +class_exists(..., true) invocations to the total number of classes +being loaded in a request). + +The Doctrine Common ClassLoader does not fail silently, by design. +It therefore does not need any costly checks for file existence. A +ClassLoader is always responsible for all classes with a certain +namespace prefix and if a class is requested to be loaded and can +not be found this is considered to be a fatal error. This also +means that using class_exists(..., true) to check for class +existence when using a Doctrine Common ClassLoader is not possible +but this is not a bad thing. What class\_exists(..., true) actually +means is two things: 1) Check whether the class is already +defined/exists (i.e. class_exists(..., false)) and if not 2) check +whether a class file can be loaded for that class. In the Doctrine +Common ClassLoader the two responsibilities of loading a class and +checking for its existence are separated, which can be observed by +the existence of the two methods ``loadClass`` and +``canLoadClass``. Thereby ``loadClass`` does not invoke +``canLoadClass`` internally, by design. However, you are free to +use it yourself to check whether a class can be loaded and the +following code snippet is thus equivalent to class\_exists(..., +true): + +.. code-block:: php + + canLoadClass('Foo')) { + // ... + } + +The only problem with this is that it is inconvenient as you need +to have a reference to the class loaders around (and there are +often multiple class loaders in use). Therefore, a simpler +alternative exists for the cases in which you really want to ask +all installed class loaders whether they can load the class: +``ClassLoader::classExists($className)``: + +.. code-block:: php + + ClassLoader is an autoloader for class files that can be + * installed on the SPL autoload stack. It is a class loader that either loads only classes + * of a specific namespace or all namespaces and it is suitable for working together + * with other autoloaders in the SPL autoload stack. + * + * If no include path is configured through the constructor or {@link setIncludePath}, a ClassLoader + * relies on the PHP include_path. + * + * @author Roman Borschel + * @since 2.0 + * + * @deprecated The ClassLoader is deprecated and will be removed in version 4.0 of doctrine/common. + */ +class ClassLoader +{ + /** + * PHP file extension. + * + * @var string + */ + protected $fileExtension = '.php'; + + /** + * Current namespace. + * + * @var string|null + */ + protected $namespace; + + /** + * Current include path. + * + * @var string|null + */ + protected $includePath; + + /** + * PHP namespace separator. + * + * @var string + */ + protected $namespaceSeparator = '\\'; + + /** + * Creates a new ClassLoader that loads classes of the + * specified namespace from the specified include path. + * + * If no include path is given, the ClassLoader relies on the PHP include_path. + * If neither a namespace nor an include path is given, the ClassLoader will + * be responsible for loading all classes, thereby relying on the PHP include_path. + * + * @param string|null $ns The namespace of the classes to load. + * @param string|null $includePath The base include path to use. + */ + public function __construct($ns = null, $includePath = null) + { + $this->namespace = $ns; + $this->includePath = $includePath; + } + + /** + * Sets the namespace separator used by classes in the namespace of this ClassLoader. + * + * @param string $sep The separator to use. + * + * @return void + */ + public function setNamespaceSeparator($sep) + { + $this->namespaceSeparator = $sep; + } + + /** + * Gets the namespace separator used by classes in the namespace of this ClassLoader. + * + * @return string + */ + public function getNamespaceSeparator() + { + return $this->namespaceSeparator; + } + + /** + * Sets the base include path for all class files in the namespace of this ClassLoader. + * + * @param string|null $includePath + * + * @return void + */ + public function setIncludePath($includePath) + { + $this->includePath = $includePath; + } + + /** + * Gets the base include path for all class files in the namespace of this ClassLoader. + * + * @return string|null + */ + public function getIncludePath() + { + return $this->includePath; + } + + /** + * Sets the file extension of class files in the namespace of this ClassLoader. + * + * @param string $fileExtension + * + * @return void + */ + public function setFileExtension($fileExtension) + { + $this->fileExtension = $fileExtension; + } + + /** + * Gets the file extension of class files in the namespace of this ClassLoader. + * + * @return string + */ + public function getFileExtension() + { + return $this->fileExtension; + } + + /** + * Registers this ClassLoader on the SPL autoload stack. + * + * @return void + */ + public function register() + { + spl_autoload_register([$this, 'loadClass']); + } + + /** + * Removes this ClassLoader from the SPL autoload stack. + * + * @return void + */ + public function unregister() + { + spl_autoload_unregister([$this, 'loadClass']); + } + + /** + * Loads the given class or interface. + * + * @param string $className The name of the class to load. + * + * @return boolean TRUE if the class has been successfully loaded, FALSE otherwise. + */ + public function loadClass($className) + { + if (self::typeExists($className)) { + return true; + } + + if ( ! $this->canLoadClass($className)) { + return false; + } + + require($this->includePath !== null ? $this->includePath . DIRECTORY_SEPARATOR : '') + . str_replace($this->namespaceSeparator, DIRECTORY_SEPARATOR, $className) + . $this->fileExtension; + + return self::typeExists($className); + } + + /** + * Asks this ClassLoader whether it can potentially load the class (file) with + * the given name. + * + * @param string $className The fully-qualified name of the class. + * + * @return boolean TRUE if this ClassLoader can load the class, FALSE otherwise. + */ + public function canLoadClass($className) + { + if ($this->namespace !== null && strpos($className, $this->namespace . $this->namespaceSeparator) !== 0) { + return false; + } + + $file = str_replace($this->namespaceSeparator, DIRECTORY_SEPARATOR, $className) . $this->fileExtension; + + if ($this->includePath !== null) { + return is_file($this->includePath . DIRECTORY_SEPARATOR . $file); + } + + return (false !== stream_resolve_include_path($file)); + } + + /** + * Checks whether a class with a given name exists. A class "exists" if it is either + * already defined in the current request or if there is an autoloader on the SPL + * autoload stack that is a) responsible for the class in question and b) is able to + * load a class file in which the class definition resides. + * + * If the class is not already defined, each autoloader in the SPL autoload stack + * is asked whether it is able to tell if the class exists. If the autoloader is + * a ClassLoader, {@link canLoadClass} is used, otherwise the autoload + * function of the autoloader is invoked and expected to return a value that + * evaluates to TRUE if the class (file) exists. As soon as one autoloader reports + * that the class exists, TRUE is returned. + * + * Note that, depending on what kinds of autoloaders are installed on the SPL + * autoload stack, the class (file) might already be loaded as a result of checking + * for its existence. This is not the case with a ClassLoader, who separates + * these responsibilities. + * + * @param string $className The fully-qualified name of the class. + * + * @return boolean TRUE if the class exists as per the definition given above, FALSE otherwise. + */ + public static function classExists($className) + { + return self::typeExists($className, true); + } + + /** + * Gets the ClassLoader from the SPL autoload stack that is responsible + * for (and is able to load) the class with the given name. + * + * @param string $className The name of the class. + * + * @return ClassLoader|null The ClassLoader for the class or NULL if no such ClassLoader exists. + */ + public static function getClassLoader($className) + { + foreach (spl_autoload_functions() as $loader) { + if (is_array($loader) + && ($classLoader = reset($loader)) + && $classLoader instanceof ClassLoader + && $classLoader->canLoadClass($className) + ) { + return $classLoader; + } + } + + return null; + } + + /** + * Checks whether a given type exists + * + * @param string $type + * @param bool $autoload + * + * @return bool + */ + private static function typeExists($type, $autoload = false) + { + return class_exists($type, $autoload) + || interface_exists($type, $autoload) + || trait_exists($type, $autoload); + } +} diff --git a/vendor/doctrine/common/lib/Doctrine/Common/CommonException.php b/vendor/doctrine/common/lib/Doctrine/Common/CommonException.php new file mode 100644 index 000000000..bf60621d5 --- /dev/null +++ b/vendor/doctrine/common/lib/Doctrine/Common/CommonException.php @@ -0,0 +1,13 @@ + + * @author Guilherme Blanco + */ +interface Comparable +{ + /** + * Compares the current object to the passed $other. + * + * Returns 0 if they are semantically equal, 1 if the other object + * is less than the current one, or -1 if its more than the current one. + * + * This method should not check for identity using ===, only for semantical equality for example + * when two different DateTime instances point to the exact same Date + TZ. + * + * @param mixed $other + * + * @return int + */ + public function compareTo($other); +} diff --git a/vendor/doctrine/common/lib/Doctrine/Common/Lexer.php b/vendor/doctrine/common/lib/Doctrine/Common/Lexer.php new file mode 100644 index 000000000..8f92c2652 --- /dev/null +++ b/vendor/doctrine/common/lib/Doctrine/Common/Lexer.php @@ -0,0 +1,25 @@ + + * @author Jonathan Wage + * @author Roman Borschel + * + * @deprecated Use Doctrine\Common\Lexer\AbstractLexer from doctrine/lexer package instead. + */ +abstract class Lexer extends AbstractLexer +{ +} diff --git a/vendor/doctrine/common/lib/Doctrine/Common/Proxy/AbstractProxyFactory.php b/vendor/doctrine/common/lib/Doctrine/Common/Proxy/AbstractProxyFactory.php new file mode 100644 index 000000000..14c4ccfcb --- /dev/null +++ b/vendor/doctrine/common/lib/Doctrine/Common/Proxy/AbstractProxyFactory.php @@ -0,0 +1,246 @@ + + */ +abstract class AbstractProxyFactory +{ + /** + * Never autogenerate a proxy and rely that it was generated by some + * process before deployment. + * + * @var integer + */ + const AUTOGENERATE_NEVER = 0; + + /** + * Always generates a new proxy in every request. + * + * This is only sane during development. + * + * @var integer + */ + const AUTOGENERATE_ALWAYS = 1; + + /** + * Autogenerate the proxy class when the proxy file does not exist. + * + * This strategy causes a file exists call whenever any proxy is used the + * first time in a request. + * + * @var integer + */ + const AUTOGENERATE_FILE_NOT_EXISTS = 2; + + /** + * Generate the proxy classes using eval(). + * + * This strategy is only sane for development, and even then it gives me + * the creeps a little. + * + * @var integer + */ + const AUTOGENERATE_EVAL = 3; + + private const AUTOGENERATE_MODES = [ + self::AUTOGENERATE_NEVER, + self::AUTOGENERATE_ALWAYS, + self::AUTOGENERATE_FILE_NOT_EXISTS, + self::AUTOGENERATE_EVAL, + ]; + + /** + * @var \Doctrine\Persistence\Mapping\ClassMetadataFactory + */ + private $metadataFactory; + + /** + * @var \Doctrine\Common\Proxy\ProxyGenerator the proxy generator responsible for creating the proxy classes/files. + */ + private $proxyGenerator; + + /** + * @var int Whether to automatically (re)generate proxy classes. + */ + private $autoGenerate; + + /** + * @var \Doctrine\Common\Proxy\ProxyDefinition[] + */ + private $definitions = []; + + /** + * @param \Doctrine\Common\Proxy\ProxyGenerator $proxyGenerator + * @param \Doctrine\Persistence\Mapping\ClassMetadataFactory $metadataFactory + * @param bool|int $autoGenerate + * + * @throws \Doctrine\Common\Proxy\Exception\InvalidArgumentException When auto generate mode is not valid. + */ + public function __construct(ProxyGenerator $proxyGenerator, ClassMetadataFactory $metadataFactory, $autoGenerate) + { + $this->proxyGenerator = $proxyGenerator; + $this->metadataFactory = $metadataFactory; + $this->autoGenerate = (int) $autoGenerate; + + if ( ! in_array($this->autoGenerate, self::AUTOGENERATE_MODES, true)) { + throw InvalidArgumentException::invalidAutoGenerateMode($autoGenerate); + } + } + + /** + * Gets a reference proxy instance for the entity of the given type and identified by + * the given identifier. + * + * @param string $className + * @param array $identifier + * + * @return \Doctrine\Common\Proxy\Proxy + * + * @throws \Doctrine\Common\Proxy\Exception\OutOfBoundsException + */ + public function getProxy($className, array $identifier) + { + $definition = isset($this->definitions[$className]) + ? $this->definitions[$className] + : $this->getProxyDefinition($className); + $fqcn = $definition->proxyClassName; + $proxy = new $fqcn($definition->initializer, $definition->cloner); + + foreach ($definition->identifierFields as $idField) { + if ( ! isset($identifier[$idField])) { + throw OutOfBoundsException::missingPrimaryKeyValue($className, $idField); + } + + $definition->reflectionFields[$idField]->setValue($proxy, $identifier[$idField]); + } + + return $proxy; + } + + /** + * Generates proxy classes for all given classes. + * + * @param \Doctrine\Persistence\Mapping\ClassMetadata[] $classes The classes (ClassMetadata instances) + * for which to generate proxies. + * @param string $proxyDir The target directory of the proxy classes. If not specified, the + * directory configured on the Configuration of the EntityManager used + * by this factory is used. + * @return int Number of generated proxies. + */ + public function generateProxyClasses(array $classes, $proxyDir = null) + { + $generated = 0; + + foreach ($classes as $class) { + if ($this->skipClass($class)) { + continue; + } + + $proxyFileName = $this->proxyGenerator->getProxyFileName($class->getName(), $proxyDir); + + $this->proxyGenerator->generateProxyClass($class, $proxyFileName); + + $generated += 1; + } + + return $generated; + } + + /** + * Reset initialization/cloning logic for an un-initialized proxy + * + * @param \Doctrine\Common\Proxy\Proxy $proxy + * + * @return \Doctrine\Common\Proxy\Proxy + * + * @throws \Doctrine\Common\Proxy\Exception\InvalidArgumentException + */ + public function resetUninitializedProxy(Proxy $proxy) + { + if ($proxy->__isInitialized()) { + throw InvalidArgumentException::unitializedProxyExpected($proxy); + } + + $className = ClassUtils::getClass($proxy); + $definition = isset($this->definitions[$className]) + ? $this->definitions[$className] + : $this->getProxyDefinition($className); + + $proxy->__setInitializer($definition->initializer); + $proxy->__setCloner($definition->cloner); + + return $proxy; + } + + /** + * Get a proxy definition for the given class name. + * + * @param string $className + * + * @return ProxyDefinition + */ + private function getProxyDefinition($className) + { + $classMetadata = $this->metadataFactory->getMetadataFor($className); + $className = $classMetadata->getName(); // aliases and case sensitivity + + $this->definitions[$className] = $this->createProxyDefinition($className); + $proxyClassName = $this->definitions[$className]->proxyClassName; + + if ( ! class_exists($proxyClassName, false)) { + $fileName = $this->proxyGenerator->getProxyFileName($className); + + switch ($this->autoGenerate) { + case self::AUTOGENERATE_NEVER: + require $fileName; + break; + + case self::AUTOGENERATE_FILE_NOT_EXISTS: + if ( ! file_exists($fileName)) { + $this->proxyGenerator->generateProxyClass($classMetadata, $fileName); + } + require $fileName; + break; + + case self::AUTOGENERATE_ALWAYS: + $this->proxyGenerator->generateProxyClass($classMetadata, $fileName); + require $fileName; + break; + + case self::AUTOGENERATE_EVAL: + $this->proxyGenerator->generateProxyClass($classMetadata, false); + break; + } + } + + return $this->definitions[$className]; + } + + /** + * Determine if this class should be skipped during proxy generation. + * + * @param \Doctrine\Persistence\Mapping\ClassMetadata $metadata + * + * @return bool + */ + abstract protected function skipClass(ClassMetadata $metadata); + + /** + * @param string $className + * + * @return ProxyDefinition + */ + abstract protected function createProxyDefinition($className); +} + +interface_exists(ClassMetadata::class); +interface_exists(ClassMetadataFactory::class); diff --git a/vendor/doctrine/common/lib/Doctrine/Common/Proxy/Autoloader.php b/vendor/doctrine/common/lib/Doctrine/Common/Proxy/Autoloader.php new file mode 100644 index 000000000..72eb26144 --- /dev/null +++ b/vendor/doctrine/common/lib/Doctrine/Common/Proxy/Autoloader.php @@ -0,0 +1,80 @@ + + * + * @internal + */ +class Autoloader +{ + /** + * Resolves proxy class name to a filename based on the following pattern. + * + * 1. Remove Proxy namespace from class name. + * 2. Remove namespace separators from remaining class name. + * 3. Return PHP filename from proxy-dir with the result from 2. + * + * @param string $proxyDir + * @param string $proxyNamespace + * @param string $className + * + * @return string + * + * @throws InvalidArgumentException + */ + public static function resolveFile($proxyDir, $proxyNamespace, $className) + { + if (0 !== strpos($className, $proxyNamespace)) { + throw InvalidArgumentException::notProxyClass($className, $proxyNamespace); + } + + // remove proxy namespace from class name + $classNameRelativeToProxyNamespace = substr($className, strlen($proxyNamespace)); + + // remove namespace separators from remaining class name + $fileName = str_replace('\\', '', $classNameRelativeToProxyNamespace); + + return $proxyDir . DIRECTORY_SEPARATOR . $fileName . '.php'; + } + + /** + * Registers and returns autoloader callback for the given proxy dir and namespace. + * + * @param string $proxyDir + * @param string $proxyNamespace + * @param callable|null $notFoundCallback Invoked when the proxy file is not found. + * + * @return \Closure + * + * @throws InvalidArgumentException + */ + public static function register($proxyDir, $proxyNamespace, $notFoundCallback = null) + { + $proxyNamespace = ltrim($proxyNamespace, '\\'); + + if ( ! (null === $notFoundCallback || is_callable($notFoundCallback))) { + throw InvalidArgumentException::invalidClassNotFoundCallback($notFoundCallback); + } + + $autoloader = function ($className) use ($proxyDir, $proxyNamespace, $notFoundCallback) { + if (0 === strpos($className, $proxyNamespace)) { + $file = Autoloader::resolveFile($proxyDir, $proxyNamespace, $className); + + if ($notFoundCallback && ! file_exists($file)) { + call_user_func($notFoundCallback, $proxyDir, $proxyNamespace, $className); + } + + require $file; + } + }; + + spl_autoload_register($autoloader); + + return $autoloader; + } +} diff --git a/vendor/doctrine/common/lib/Doctrine/Common/Proxy/Exception/InvalidArgumentException.php b/vendor/doctrine/common/lib/Doctrine/Common/Proxy/Exception/InvalidArgumentException.php new file mode 100644 index 000000000..e993cb930 --- /dev/null +++ b/vendor/doctrine/common/lib/Doctrine/Common/Proxy/Exception/InvalidArgumentException.php @@ -0,0 +1,106 @@ + + */ +class InvalidArgumentException extends BaseInvalidArgumentException implements ProxyException +{ + /** + * @return self + */ + public static function proxyDirectoryRequired() + { + return new self('You must configure a proxy directory. See docs for details'); + } + + /** + * @param string $className + * @param string $proxyNamespace + * + * @return self + */ + public static function notProxyClass($className, $proxyNamespace) + { + return new self(sprintf('The class "%s" is not part of the proxy namespace "%s"', $className, $proxyNamespace)); + } + + /** + * @param string $name + * + * @return self + */ + public static function invalidPlaceholder($name) + { + return new self(sprintf('Provided placeholder for "%s" must be either a string or a valid callable', $name)); + } + + /** + * @return self + */ + public static function proxyNamespaceRequired() + { + return new self('You must configure a proxy namespace'); + } + + /** + * @param Proxy $proxy + * + * @return self + */ + public static function unitializedProxyExpected(Proxy $proxy) + { + return new self(sprintf('Provided proxy of type "%s" must not be initialized.', get_class($proxy))); + } + + /** + * @param mixed $callback + * + * @return self + */ + public static function invalidClassNotFoundCallback($callback) + { + $type = is_object($callback) ? get_class($callback) : gettype($callback); + + return new self(sprintf('Invalid \$notFoundCallback given: must be a callable, "%s" given', $type)); + } + + /** + * @param string $className + * + * @return self + */ + public static function classMustNotBeAbstract($className) + { + return new self(sprintf('Unable to create a proxy for an abstract class "%s".', $className)); + } + + /** + * @param string $className + * + * @return self + */ + public static function classMustNotBeFinal($className) + { + return new self(sprintf('Unable to create a proxy for a final class "%s".', $className)); + } + + /** + * @param mixed $value + * + * @return self + */ + public static function invalidAutoGenerateMode($value) : self + { + return new self(sprintf('Invalid auto generate mode "%s" given.', $value)); + } +} + +interface_exists(Proxy::class); diff --git a/vendor/doctrine/common/lib/Doctrine/Common/Proxy/Exception/OutOfBoundsException.php b/vendor/doctrine/common/lib/Doctrine/Common/Proxy/Exception/OutOfBoundsException.php new file mode 100644 index 000000000..8044aafcd --- /dev/null +++ b/vendor/doctrine/common/lib/Doctrine/Common/Proxy/Exception/OutOfBoundsException.php @@ -0,0 +1,24 @@ + + */ +class OutOfBoundsException extends BaseOutOfBoundsException implements ProxyException +{ + /** + * @param string $className + * @param string $idField + * + * @return self + */ + public static function missingPrimaryKeyValue($className, $idField) + { + return new self(sprintf("Missing value for primary key %s on %s", $idField, $className)); + } +} diff --git a/vendor/doctrine/common/lib/Doctrine/Common/Proxy/Exception/ProxyException.php b/vendor/doctrine/common/lib/Doctrine/Common/Proxy/Exception/ProxyException.php new file mode 100644 index 000000000..a06599faf --- /dev/null +++ b/vendor/doctrine/common/lib/Doctrine/Common/Proxy/Exception/ProxyException.php @@ -0,0 +1,13 @@ + + */ +interface ProxyException +{ +} diff --git a/vendor/doctrine/common/lib/Doctrine/Common/Proxy/Exception/UnexpectedValueException.php b/vendor/doctrine/common/lib/Doctrine/Common/Proxy/Exception/UnexpectedValueException.php new file mode 100644 index 000000000..b25b37b62 --- /dev/null +++ b/vendor/doctrine/common/lib/Doctrine/Common/Proxy/Exception/UnexpectedValueException.php @@ -0,0 +1,70 @@ + + */ +class UnexpectedValueException extends BaseUnexpectedValueException implements ProxyException +{ + /** + * @param string $proxyDirectory + * + * @return self + */ + public static function proxyDirectoryNotWritable($proxyDirectory) + { + return new self(sprintf('Your proxy directory "%s" must be writable', $proxyDirectory)); + } + + /** + * @param string $className + * @param string $methodName + * @param string $parameterName + * @param \Exception|null $previous + * + * @return self + */ + public static function invalidParameterTypeHint( + $className, + $methodName, + $parameterName, + \Exception $previous = null + ) { + return new self( + sprintf( + 'The type hint of parameter "%s" in method "%s" in class "%s" is invalid.', + $parameterName, + $methodName, + $className + ), + 0, + $previous + ); + } + + /** + * @param string $className + * @param string $methodName + * @param \Exception|null $previous + * + * @return self + */ + public static function invalidReturnTypeHint($className, $methodName, \Exception $previous = null) + { + return new self( + sprintf( + 'The return type of method "%s" in class "%s" is invalid.', + $methodName, + $className + ), + 0, + $previous + ); + } +} diff --git a/vendor/doctrine/common/lib/Doctrine/Common/Proxy/Proxy.php b/vendor/doctrine/common/lib/Doctrine/Common/Proxy/Proxy.php new file mode 100644 index 000000000..50eba01cb --- /dev/null +++ b/vendor/doctrine/common/lib/Doctrine/Common/Proxy/Proxy.php @@ -0,0 +1,72 @@ + + * @author Marco Pivetta + * @since 2.4 + */ +interface Proxy extends BaseProxy +{ + /** + * Marks the proxy as initialized or not. + * + * @param boolean $initialized + * + * @return void + */ + public function __setInitialized($initialized); + + /** + * Sets the initializer callback to be used when initializing the proxy. That + * initializer should accept 3 parameters: $proxy, $method and $params. Those + * are respectively the proxy object that is being initialized, the method name + * that triggered initialization and the parameters passed to that method. + * + * @param Closure|null $initializer + * + * @return void + */ + public function __setInitializer(Closure $initializer = null); + + /** + * Retrieves the initializer callback used to initialize the proxy. + * + * @see __setInitializer + * + * @return Closure|null + */ + public function __getInitializer(); + + /** + * Sets the callback to be used when cloning the proxy. That initializer should accept + * a single parameter, which is the cloned proxy instance itself. + * + * @param Closure|null $cloner + * + * @return void + */ + public function __setCloner(Closure $cloner = null); + + /** + * Retrieves the callback to be used when cloning the proxy. + * + * @see __setCloner + * + * @return Closure|null + */ + public function __getCloner(); + + /** + * Retrieves the list of lazy loaded properties for a given proxy + * + * @return array Keys are the property names, and values are the default values + * for those properties. + */ + public function __getLazyProperties(); +} diff --git a/vendor/doctrine/common/lib/Doctrine/Common/Proxy/ProxyDefinition.php b/vendor/doctrine/common/lib/Doctrine/Common/Proxy/ProxyDefinition.php new file mode 100644 index 000000000..e81036f2e --- /dev/null +++ b/vendor/doctrine/common/lib/Doctrine/Common/Proxy/ProxyDefinition.php @@ -0,0 +1,51 @@ + + */ +class ProxyDefinition +{ + /** + * @var string + */ + public $proxyClassName; + + /** + * @var array + */ + public $identifierFields; + + /** + * @var \ReflectionProperty[] + */ + public $reflectionFields; + + /** + * @var callable + */ + public $initializer; + + /** + * @var callable + */ + public $cloner; + + /** + * @param string $proxyClassName + * @param array $identifierFields + * @param array $reflectionFields + * @param callable $initializer + * @param callable $cloner + */ + public function __construct($proxyClassName, array $identifierFields, array $reflectionFields, $initializer, $cloner) + { + $this->proxyClassName = $proxyClassName; + $this->identifierFields = $identifierFields; + $this->reflectionFields = $reflectionFields; + $this->initializer = $initializer; + $this->cloner = $cloner; + } +} diff --git a/vendor/doctrine/common/lib/Doctrine/Common/Proxy/ProxyGenerator.php b/vendor/doctrine/common/lib/Doctrine/Common/Proxy/ProxyGenerator.php new file mode 100644 index 000000000..464706823 --- /dev/null +++ b/vendor/doctrine/common/lib/Doctrine/Common/Proxy/ProxyGenerator.php @@ -0,0 +1,1152 @@ + + * @since 2.4 + */ +class ProxyGenerator +{ + /** + * Used to match very simple id methods that don't need + * to be decorated since the identifier is known. + */ + const PATTERN_MATCH_ID_METHOD = '((public\s+)?(function\s+%s\s*\(\)\s*)\s*(?::\s*\??\s*\\\\?[a-z_\x7f-\xff][\w\x7f-\xff]*(?:\\\\[a-z_\x7f-\xff][\w\x7f-\xff]*)*\s*)?{\s*return\s*\$this->%s;\s*})i'; + + /** + * The namespace that contains all proxy classes. + * + * @var string + */ + private $proxyNamespace; + + /** + * The directory that contains all proxy classes. + * + * @var string + */ + private $proxyDirectory; + + /** + * Map of callables used to fill in placeholders set in the template. + * + * @var string[]|callable[] + */ + protected $placeholders = [ + 'baseProxyInterface' => Proxy::class, + 'additionalProperties' => '', + ]; + + /** + * Template used as a blueprint to generate proxies. + * + * @var string + */ + protected $proxyClassTemplate = '; + +/** + * DO NOT EDIT THIS FILE - IT WAS CREATED BY DOCTRINE\'S PROXY GENERATOR + */ +class extends \ implements \ +{ + /** + * @var \Closure the callback responsible for loading properties in the proxy object. This callback is called with + * three parameters, being respectively the proxy object to be initialized, the method that triggered the + * initialization process and an array of ordered parameters that were passed to that method. + * + * @see \Doctrine\Common\Proxy\Proxy::__setInitializer + */ + public $__initializer__; + + /** + * @var \Closure the callback responsible of loading properties that need to be copied in the cloned object + * + * @see \Doctrine\Common\Proxy\Proxy::__setCloner + */ + public $__cloner__; + + /** + * @var boolean flag indicating if this object was already initialized + * + * @see \Doctrine\Persistence\Proxy::__isInitialized + */ + public $__isInitialized__ = false; + + /** + * @var array properties to be lazy loaded, indexed by property name + */ + public static $lazyPropertiesNames = ; + + /** + * @var array default values of properties to be lazy loaded, with keys being the property names + * + * @see \Doctrine\Common\Proxy\Proxy::__getLazyProperties + */ + public static $lazyPropertiesDefaults = ; + + + + + + + + + + + + + + + + + + /** + * Forces initialization of the proxy + */ + public function __load() + { + $this->__initializer__ && $this->__initializer__->__invoke($this, \'__load\', []); + } + + /** + * {@inheritDoc} + * @internal generated method: use only when explicitly handling proxy specific loading logic + */ + public function __isInitialized() + { + return $this->__isInitialized__; + } + + /** + * {@inheritDoc} + * @internal generated method: use only when explicitly handling proxy specific loading logic + */ + public function __setInitialized($initialized) + { + $this->__isInitialized__ = $initialized; + } + + /** + * {@inheritDoc} + * @internal generated method: use only when explicitly handling proxy specific loading logic + */ + public function __setInitializer(\Closure $initializer = null) + { + $this->__initializer__ = $initializer; + } + + /** + * {@inheritDoc} + * @internal generated method: use only when explicitly handling proxy specific loading logic + */ + public function __getInitializer() + { + return $this->__initializer__; + } + + /** + * {@inheritDoc} + * @internal generated method: use only when explicitly handling proxy specific loading logic + */ + public function __setCloner(\Closure $cloner = null) + { + $this->__cloner__ = $cloner; + } + + /** + * {@inheritDoc} + * @internal generated method: use only when explicitly handling proxy specific cloning logic + */ + public function __getCloner() + { + return $this->__cloner__; + } + + /** + * {@inheritDoc} + * @internal generated method: use only when explicitly handling proxy specific loading logic + * @deprecated no longer in use - generated code now relies on internal components rather than generated public API + * @static + */ + public function __getLazyProperties() + { + return self::$lazyPropertiesDefaults; + } + + +} +'; + + /** + * Initializes a new instance of the ProxyFactory class that is + * connected to the given EntityManager. + * + * @param string $proxyDirectory The directory to use for the proxy classes. It must exist. + * @param string $proxyNamespace The namespace to use for the proxy classes. + * + * @throws InvalidArgumentException + */ + public function __construct($proxyDirectory, $proxyNamespace) + { + if ( ! $proxyDirectory) { + throw InvalidArgumentException::proxyDirectoryRequired(); + } + + if ( ! $proxyNamespace) { + throw InvalidArgumentException::proxyNamespaceRequired(); + } + + $this->proxyDirectory = $proxyDirectory; + $this->proxyNamespace = $proxyNamespace; + } + + /** + * Sets a placeholder to be replaced in the template. + * + * @param string $name + * @param string|callable $placeholder + * + * @throws InvalidArgumentException + */ + public function setPlaceholder($name, $placeholder) + { + if ( ! is_string($placeholder) && ! is_callable($placeholder)) { + throw InvalidArgumentException::invalidPlaceholder($name); + } + + $this->placeholders[$name] = $placeholder; + } + + /** + * Sets the base template used to create proxy classes. + * + * @param string $proxyClassTemplate + */ + public function setProxyClassTemplate($proxyClassTemplate) + { + $this->proxyClassTemplate = (string) $proxyClassTemplate; + } + + /** + * Generates a proxy class file. + * + * @param \Doctrine\Persistence\Mapping\ClassMetadata $class Metadata for the original class. + * @param string|bool $fileName Filename (full path) for the generated class. If none is given, eval() is used. + * + * @throws InvalidArgumentException + * @throws UnexpectedValueException + */ + public function generateProxyClass(ClassMetadata $class, $fileName = false) + { + $this->verifyClassCanBeProxied($class); + + preg_match_all('(<([a-zA-Z]+)>)', $this->proxyClassTemplate, $placeholderMatches); + + $placeholderMatches = array_combine($placeholderMatches[0], $placeholderMatches[1]); + $placeholders = []; + + foreach ($placeholderMatches as $placeholder => $name) { + $placeholders[$placeholder] = isset($this->placeholders[$name]) + ? $this->placeholders[$name] + : [$this, 'generate' . $name]; + } + + foreach ($placeholders as & $placeholder) { + if (is_callable($placeholder)) { + $placeholder = call_user_func($placeholder, $class); + } + } + + $proxyCode = strtr($this->proxyClassTemplate, $placeholders); + + if ( ! $fileName) { + $proxyClassName = $this->generateNamespace($class) . '\\' . $this->generateProxyShortClassName($class); + + if ( ! class_exists($proxyClassName)) { + eval(substr($proxyCode, 5)); + } + + return; + } + + $parentDirectory = dirname($fileName); + + if ( ! is_dir($parentDirectory) && (false === @mkdir($parentDirectory, 0775, true))) { + throw UnexpectedValueException::proxyDirectoryNotWritable($this->proxyDirectory); + } + + if ( ! is_writable($parentDirectory)) { + throw UnexpectedValueException::proxyDirectoryNotWritable($this->proxyDirectory); + } + + $tmpFileName = $fileName . '.' . uniqid('', true); + + file_put_contents($tmpFileName, $proxyCode); + @chmod($tmpFileName, 0664); + rename($tmpFileName, $fileName); + } + + /** + * @param ClassMetadata $class + * + * @throws InvalidArgumentException + */ + private function verifyClassCanBeProxied(ClassMetadata $class) + { + if ($class->getReflectionClass()->isFinal()) { + throw InvalidArgumentException::classMustNotBeFinal($class->getName()); + } + + if ($class->getReflectionClass()->isAbstract()) { + throw InvalidArgumentException::classMustNotBeAbstract($class->getName()); + } + } + + /** + * Generates the proxy short class name to be used in the template. + * + * @param \Doctrine\Persistence\Mapping\ClassMetadata $class + * + * @return string + */ + private function generateProxyShortClassName(ClassMetadata $class) + { + $proxyClassName = ClassUtils::generateProxyClassName($class->getName(), $this->proxyNamespace); + $parts = explode('\\', strrev($proxyClassName), 2); + + return strrev($parts[0]); + } + + /** + * Generates the proxy namespace. + * + * @param \Doctrine\Persistence\Mapping\ClassMetadata $class + * + * @return string + */ + private function generateNamespace(ClassMetadata $class) + { + $proxyClassName = ClassUtils::generateProxyClassName($class->getName(), $this->proxyNamespace); + $parts = explode('\\', strrev($proxyClassName), 2); + + return strrev($parts[1]); + } + + /** + * Generates the original class name. + * + * @param \Doctrine\Persistence\Mapping\ClassMetadata $class + * + * @return string + */ + private function generateClassName(ClassMetadata $class) + { + return ltrim($class->getName(), '\\'); + } + + /** + * Generates the array representation of lazy loaded public properties and their default values. + * + * @param \Doctrine\Persistence\Mapping\ClassMetadata $class + * + * @return string + */ + private function generateLazyPropertiesNames(ClassMetadata $class) + { + $lazyPublicProperties = $this->getLazyLoadedPublicPropertiesNames($class); + $values = []; + + foreach ($lazyPublicProperties as $name) { + $values[$name] = null; + } + + return var_export($values, true); + } + + /** + * Generates the array representation of lazy loaded public properties names. + * + * @param \Doctrine\Persistence\Mapping\ClassMetadata $class + * + * @return string + */ + private function generateLazyPropertiesDefaults(ClassMetadata $class) + { + return var_export($this->getLazyLoadedPublicProperties($class), true); + } + + /** + * Generates the constructor code (un-setting public lazy loaded properties, setting identifier field values). + * + * @param \Doctrine\Persistence\Mapping\ClassMetadata $class + * + * @return string + */ + private function generateConstructorImpl(ClassMetadata $class) + { + $constructorImpl = <<<'EOT' + public function __construct(?\Closure $initializer = null, ?\Closure $cloner = null) + { + +EOT; + + $toUnset = array_map(static function (string $name) : string { + return '$this->' . $name; + }, $this->getLazyLoadedPublicPropertiesNames($class)); + + $constructorImpl .= ($toUnset === [] ? '' : ' unset(' . implode(', ', $toUnset) . ");\n") + . <<<'EOT' + + $this->__initializer__ = $initializer; + $this->__cloner__ = $cloner; + } +EOT; + + return $constructorImpl; + } + + /** + * Generates the magic getter invoked when lazy loaded public properties are requested. + * + * @param \Doctrine\Persistence\Mapping\ClassMetadata $class + * + * @return string + */ + private function generateMagicGet(ClassMetadata $class) + { + $lazyPublicProperties = $this->getLazyLoadedPublicPropertiesNames($class); + $reflectionClass = $class->getReflectionClass(); + $hasParentGet = false; + $returnReference = ''; + $inheritDoc = ''; + $name = '$name'; + $parametersString = '$name'; + $returnTypeHint = null; + + if ($reflectionClass->hasMethod('__get')) { + $hasParentGet = true; + $inheritDoc = '{@inheritDoc}'; + $methodReflection = $reflectionClass->getMethod('__get'); + + if ($methodReflection->returnsReference()) { + $returnReference = '& '; + } + + $methodParameters = $methodReflection->getParameters(); + $name = '$' . $methodParameters[0]->getName(); + + $parametersString = $this->buildParametersString($methodReflection->getParameters(), ['name']); + $returnTypeHint = $this->getMethodReturnType($methodReflection); + } + + if (empty($lazyPublicProperties) && ! $hasParentGet) { + return ''; + } + + $magicGet = <<__initializer__ && $this->__initializer__->__invoke($this, '__get', [$name]); +EOT; + + if ($returnTypeHint === ': void') { + $magicGet .= "\n return;"; + } else { + $magicGet .= "\n return \$this->\$name;"; + } + + $magicGet .= <<<'EOT' + + } + + +EOT; + } + + if ($hasParentGet) { + $magicGet .= <<<'EOT' + $this->__initializer__ && $this->__initializer__->__invoke($this, '__get', [$name]); +EOT; + + if ($returnTypeHint === ': void') { + $magicGet .= <<<'EOT' + + parent::__get($name); + return; +EOT; + } else { + $magicGet .= <<<'EOT' + + return parent::__get($name); +EOT; + } + } else { + $magicGet .= sprintf(<<getLazyLoadedPublicPropertiesNames($class); + $hasParentSet = $class->getReflectionClass()->hasMethod('__set'); + $parametersString = '$name, $value'; + $returnTypeHint = null; + + if ($hasParentSet) { + $methodReflection = $class->getReflectionClass()->getMethod('__set'); + $parametersString = $this->buildParametersString($methodReflection->getParameters(), ['name', 'value']); + $returnTypeHint = $this->getMethodReturnType($methodReflection); + } + + if (empty($lazyPublicProperties) && ! $hasParentSet) { + return ''; + } + + $inheritDoc = $hasParentSet ? '{@inheritDoc}' : ''; + $magicSet = sprintf(<<<'EOT' + /** + * %s + * @param string $name + * @param mixed $value + */ + public function __set(%s)%s + { + +EOT + , $inheritDoc, $parametersString, $returnTypeHint); + + if ( ! empty($lazyPublicProperties)) { + $magicSet .= <<<'EOT' + if (\array_key_exists($name, self::$lazyPropertiesNames)) { + $this->__initializer__ && $this->__initializer__->__invoke($this, '__set', [$name, $value]); + + $this->$name = $value; + + return; + } + + +EOT; + } + + if ($hasParentSet) { + $magicSet .= <<<'EOT' + $this->__initializer__ && $this->__initializer__->__invoke($this, '__set', [$name, $value]); + + return parent::__set($name, $value); +EOT; + } else { + $magicSet .= " \$this->\$name = \$value;"; + } + + return $magicSet . "\n }"; + } + + /** + * Generates the magic issetter invoked when lazy loaded public properties are checked against isset(). + * + * @param \Doctrine\Persistence\Mapping\ClassMetadata $class + * + * @return string + */ + private function generateMagicIsset(ClassMetadata $class) + { + $lazyPublicProperties = $this->getLazyLoadedPublicPropertiesNames($class); + $hasParentIsset = $class->getReflectionClass()->hasMethod('__isset'); + $parametersString = '$name'; + $returnTypeHint = null; + + if ($hasParentIsset) { + $methodReflection = $class->getReflectionClass()->getMethod('__isset'); + $parametersString = $this->buildParametersString($methodReflection->getParameters(), ['name']); + $returnTypeHint = $this->getMethodReturnType($methodReflection); + } + + if (empty($lazyPublicProperties) && ! $hasParentIsset) { + return ''; + } + + $inheritDoc = $hasParentIsset ? '{@inheritDoc}' : ''; + $magicIsset = <<__initializer__ && $this->__initializer__->__invoke($this, '__isset', [$name]); + + return isset($this->$name); + } + + +EOT; + } + + if ($hasParentIsset) { + $magicIsset .= <<<'EOT' + $this->__initializer__ && $this->__initializer__->__invoke($this, '__isset', [$name]); + + return parent::__isset($name); +EOT; + } else { + $magicIsset .= " return false;"; + } + + return $magicIsset . "\n }"; + } + + /** + * Generates implementation for the `__sleep` method of proxies. + * + * @param \Doctrine\Persistence\Mapping\ClassMetadata $class + * + * @return string + */ + private function generateSleepImpl(ClassMetadata $class) + { + $hasParentSleep = $class->getReflectionClass()->hasMethod('__sleep'); + $inheritDoc = $hasParentSleep ? '{@inheritDoc}' : ''; + $sleepImpl = <<__isInitialized__) { + $properties = array_diff($properties, array_keys(self::$lazyPropertiesNames)); + } + + return $properties; + } +EOT; + } + + $allProperties = ['__isInitialized__']; + + /* @var $prop \ReflectionProperty */ + foreach ($class->getReflectionClass()->getProperties() as $prop) { + if ($prop->isStatic()) { + continue; + } + + $allProperties[] = $prop->isPrivate() + ? "\0" . $prop->getDeclaringClass()->getName() . "\0" . $prop->getName() + : $prop->getName(); + } + + $lazyPublicProperties = $this->getLazyLoadedPublicPropertiesNames($class); + $protectedProperties = array_diff($allProperties, $lazyPublicProperties); + + foreach ($allProperties as &$property) { + $property = var_export($property, true); + } + + foreach ($protectedProperties as &$property) { + $property = var_export($property, true); + } + + $allProperties = implode(', ', $allProperties); + $protectedProperties = implode(', ', $protectedProperties); + + return $sleepImpl . <<__isInitialized__) { + return [$allProperties]; + } + + return [$protectedProperties]; + } +EOT; + } + + /** + * Generates implementation for the `__wakeup` method of proxies. + * + * @param \Doctrine\Persistence\Mapping\ClassMetadata $class + * + * @return string + */ + private function generateWakeupImpl(ClassMetadata $class) + { + $unsetPublicProperties = []; + $hasWakeup = $class->getReflectionClass()->hasMethod('__wakeup'); + + foreach ($this->getLazyLoadedPublicPropertiesNames($class) as $lazyPublicProperty) { + $unsetPublicProperties[] = '$this->' . $lazyPublicProperty; + } + + $shortName = $this->generateProxyShortClassName($class); + $inheritDoc = $hasWakeup ? '{@inheritDoc}' : ''; + $wakeupImpl = <<__isInitialized__) { + \$this->__initializer__ = function ($shortName \$proxy) { + \$proxy->__setInitializer(null); + \$proxy->__setCloner(null); + + \$existingProperties = get_object_vars(\$proxy); + + foreach (\$proxy::\$lazyPropertiesDefaults as \$property => \$defaultValue) { + if ( ! array_key_exists(\$property, \$existingProperties)) { + \$proxy->\$property = \$defaultValue; + } + } + }; + +EOT; + + if ( ! empty($unsetPublicProperties)) { + $wakeupImpl .= "\n unset(" . implode(', ', $unsetPublicProperties) . ");"; + } + + $wakeupImpl .= "\n }"; + + if ($hasWakeup) { + $wakeupImpl .= "\n parent::__wakeup();"; + } + + $wakeupImpl .= "\n }"; + + return $wakeupImpl; + } + + /** + * Generates implementation for the `__clone` method of proxies. + * + * @param \Doctrine\Persistence\Mapping\ClassMetadata $class + * + * @return string + */ + private function generateCloneImpl(ClassMetadata $class) + { + $hasParentClone = $class->getReflectionClass()->hasMethod('__clone'); + $inheritDoc = $hasParentClone ? '{@inheritDoc}' : ''; + $callParentClone = $hasParentClone ? "\n parent::__clone();\n" : ''; + + return <<__cloner__ && \$this->__cloner__->__invoke(\$this, '__clone', []); +$callParentClone } +EOT; + } + + /** + * Generates decorated methods by picking those available in the parent class. + * + * @param \Doctrine\Persistence\Mapping\ClassMetadata $class + * + * @return string + */ + private function generateMethods(ClassMetadata $class) + { + $methods = ''; + $methodNames = []; + $reflectionMethods = $class->getReflectionClass()->getMethods(\ReflectionMethod::IS_PUBLIC); + $skippedMethods = [ + '__sleep' => true, + '__clone' => true, + '__wakeup' => true, + '__get' => true, + '__set' => true, + '__isset' => true, + ]; + + foreach ($reflectionMethods as $method) { + $name = $method->getName(); + + if ($method->isConstructor() || + isset($skippedMethods[strtolower($name)]) || + isset($methodNames[$name]) || + $method->isFinal() || + $method->isStatic() || + ( ! $method->isPublic()) + ) { + continue; + } + + $methodNames[$name] = true; + $methods .= "\n /**\n" + . " * {@inheritDoc}\n" + . " */\n" + . ' public function '; + + if ($method->returnsReference()) { + $methods .= '&'; + } + + $methods .= $name . '(' . $this->buildParametersString($method->getParameters()) . ')'; + $methods .= $this->getMethodReturnType($method); + $methods .= "\n" . ' {' . "\n"; + + if ($this->isShortIdentifierGetter($method, $class)) { + $identifier = lcfirst(substr($name, 3)); + $fieldType = $class->getTypeOfField($identifier); + $cast = in_array($fieldType, ['integer', 'smallint']) ? '(int) ' : ''; + + $methods .= ' if ($this->__isInitialized__ === false) {' . "\n"; + $methods .= ' '; + $methods .= $this->shouldProxiedMethodReturn($method) ? 'return ' : ''; + $methods .= $cast . ' parent::' . $method->getName() . "();\n"; + $methods .= ' }' . "\n\n"; + } + + $invokeParamsString = implode(', ', $this->getParameterNamesForInvoke($method->getParameters())); + $callParamsString = implode(', ', $this->getParameterNamesForParentCall($method->getParameters())); + + $methods .= "\n \$this->__initializer__ " + . "&& \$this->__initializer__->__invoke(\$this, " . var_export($name, true) + . ", [" . $invokeParamsString . "]);" + . "\n\n " + . ($this->shouldProxiedMethodReturn($method) ? 'return ' : '') + . "parent::" . $name . '(' . $callParamsString . ');' + . "\n" . ' }' . "\n"; + } + + return $methods; + } + + /** + * Generates the Proxy file name. + * + * @param string $className + * @param string $baseDirectory Optional base directory for proxy file name generation. + * If not specified, the directory configured on the Configuration of the + * EntityManager will be used by this factory. + * + * @return string + */ + public function getProxyFileName($className, $baseDirectory = null) + { + $baseDirectory = $baseDirectory ?: $this->proxyDirectory; + + return rtrim($baseDirectory, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . Proxy::MARKER + . str_replace('\\', '', $className) . '.php'; + } + + /** + * Checks if the method is a short identifier getter. + * + * What does this mean? For proxy objects the identifier is already known, + * however accessing the getter for this identifier usually triggers the + * lazy loading, leading to a query that may not be necessary if only the + * ID is interesting for the userland code (for example in views that + * generate links to the entity, but do not display anything else). + * + * @param \ReflectionMethod $method + * @param \Doctrine\Persistence\Mapping\ClassMetadata $class + * + * @return boolean + */ + private function isShortIdentifierGetter($method, ClassMetadata $class) + { + $identifier = lcfirst(substr($method->getName(), 3)); + $startLine = $method->getStartLine(); + $endLine = $method->getEndLine(); + $cheapCheck = ( + $method->getNumberOfParameters() == 0 + && substr($method->getName(), 0, 3) == 'get' + && in_array($identifier, $class->getIdentifier(), true) + && $class->hasField($identifier) + && (($endLine - $startLine) <= 4) + ); + + if ($cheapCheck) { + $code = file($method->getFileName()); + $code = trim(implode(' ', array_slice($code, $startLine - 1, $endLine - $startLine + 1))); + + $pattern = sprintf(self::PATTERN_MATCH_ID_METHOD, $method->getName(), $identifier); + + if (preg_match($pattern, $code)) { + return true; + } + } + + return false; + } + + /** + * Generates the list of public properties to be lazy loaded. + * + * @param \Doctrine\Persistence\Mapping\ClassMetadata $class + * + * @return array + */ + private function getLazyLoadedPublicPropertiesNames(ClassMetadata $class) : array + { + $properties = []; + + foreach ($class->getReflectionClass()->getProperties(\ReflectionProperty::IS_PUBLIC) as $property) { + $name = $property->getName(); + + if (($class->hasField($name) || $class->hasAssociation($name)) && ! $class->isIdentifier($name)) { + $properties[] = $name; + } + } + + return $properties; + } + + /** + * Generates the list of default values of public properties. + * + * @param \Doctrine\Persistence\Mapping\ClassMetadata $class + * + * @return mixed[] + */ + private function getLazyLoadedPublicProperties(ClassMetadata $class) + { + $defaultProperties = $class->getReflectionClass()->getDefaultProperties(); + $lazyLoadedPublicProperties = $this->getLazyLoadedPublicPropertiesNames($class); + $defaultValues = []; + + foreach ($class->getReflectionClass()->getProperties(\ReflectionProperty::IS_PUBLIC) as $property) { + $name = $property->getName(); + + if ( ! in_array($name, $lazyLoadedPublicProperties, true)) { + continue; + } + + if (array_key_exists($name, $defaultProperties)) { + $defaultValues[$name] = $defaultProperties[$name]; + } elseif (method_exists($property, 'getType')) { + $propertyType = $property->getType(); + if (null !== $propertyType && $propertyType->allowsNull()) { + $defaultValues[$name] = null; + } + } + } + + return $defaultValues; + } + + /** + * @param \ReflectionParameter[] $parameters + * @param string[] $renameParameters + * + * @return string + */ + private function buildParametersString(array $parameters, array $renameParameters = []) + { + $parameterDefinitions = []; + + /* @var $param \ReflectionParameter */ + $i = -1; + foreach ($parameters as $param) { + $i++; + $parameterDefinition = ''; + + if ($parameterType = $this->getParameterType($param)) { + $parameterDefinition .= $parameterType . ' '; + } + + if ($param->isPassedByReference()) { + $parameterDefinition .= '&'; + } + + if ($param->isVariadic()) { + $parameterDefinition .= '...'; + } + + $parameterDefinition .= '$' . ($renameParameters ? $renameParameters[$i] : $param->getName()); + + if ($param->isDefaultValueAvailable()) { + $parameterDefinition .= ' = ' . var_export($param->getDefaultValue(), true); + } + + $parameterDefinitions[] = $parameterDefinition; + } + + return implode(', ', $parameterDefinitions); + } + + /** + * @param \ReflectionParameter $parameter + * + * @return string|null + */ + private function getParameterType(\ReflectionParameter $parameter) + { + if ( ! $parameter->hasType()) { + return null; + } + + return $this->formatType($parameter->getType(), $parameter->getDeclaringFunction(), $parameter); + } + + /** + * @param \ReflectionParameter[] $parameters + * + * @return string[] + */ + private function getParameterNamesForInvoke(array $parameters) + { + return array_map( + function (\ReflectionParameter $parameter) { + return '$' . $parameter->getName(); + }, + $parameters + ); + } + + /** + * @param \ReflectionParameter[] $parameters + * + * @return string[] + */ + private function getParameterNamesForParentCall(array $parameters) + { + return array_map( + function (\ReflectionParameter $parameter) { + $name = ''; + + if ($parameter->isVariadic()) { + $name .= '...'; + } + + $name .= '$' . $parameter->getName(); + + return $name; + }, + $parameters + ); + } + + /** + * @param \ReflectionMethod $method + * + * @return string + */ + private function getMethodReturnType(\ReflectionMethod $method) + { + if ( ! $method->hasReturnType()) { + return ''; + } + + return ': ' . $this->formatType($method->getReturnType(), $method); + } + + /** + * @param \ReflectionMethod $method + * + * @return bool + */ + private function shouldProxiedMethodReturn(\ReflectionMethod $method) + { + if ( ! $method->hasReturnType()) { + return true; + } + + return 'void' !== strtolower($this->formatType($method->getReturnType(), $method)); + } + + /** + * @param \ReflectionType $type + * @param \ReflectionMethod $method + * @param \ReflectionParameter|null $parameter + * + * @return string + */ + private function formatType( + \ReflectionType $type, + \ReflectionMethod $method, + \ReflectionParameter $parameter = null + ) { + $name = $type->getName(); + $nameLower = strtolower($name); + + if ('self' === $nameLower) { + $name = $method->getDeclaringClass()->getName(); + } + + if ('parent' === $nameLower) { + $name = $method->getDeclaringClass()->getParentClass()->getName(); + } + + if ( ! $type->isBuiltin() && ! class_exists($name) && ! interface_exists($name)) { + if (null !== $parameter) { + throw UnexpectedValueException::invalidParameterTypeHint( + $method->getDeclaringClass()->getName(), + $method->getName(), + $parameter->getName() + ); + } + + throw UnexpectedValueException::invalidReturnTypeHint( + $method->getDeclaringClass()->getName(), + $method->getName() + ); + } + + if ( ! $type->isBuiltin()) { + $name = '\\' . $name; + } + + if ($type->allowsNull() + && (null === $parameter || ! $parameter->isDefaultValueAvailable() || null !== $parameter->getDefaultValue()) + ) { + $name = '?' . $name; + } + + return $name; + } +} + +interface_exists(ClassMetadata::class); diff --git a/vendor/doctrine/common/lib/Doctrine/Common/Util/ClassUtils.php b/vendor/doctrine/common/lib/Doctrine/Common/Util/ClassUtils.php new file mode 100644 index 000000000..5756a84b8 --- /dev/null +++ b/vendor/doctrine/common/lib/Doctrine/Common/Util/ClassUtils.php @@ -0,0 +1,91 @@ + + * @author Johannes Schmitt + */ +class ClassUtils +{ + /** + * Gets the real class name of a class name that could be a proxy. + * + * @param string $class + * + * @return string + */ + public static function getRealClass($class) + { + if (false === $pos = strrpos($class, '\\' . Proxy::MARKER . '\\')) { + return $class; + } + + return substr($class, $pos + Proxy::MARKER_LENGTH + 2); + } + + /** + * Gets the real class name of an object (even if its a proxy). + * + * @param object $object + * + * @return string + */ + public static function getClass($object) + { + return self::getRealClass(get_class($object)); + } + + /** + * Gets the real parent class name of a class or object. + * + * @param string $className + * + * @return string + */ + public static function getParentClass($className) + { + return get_parent_class(self::getRealClass($className)); + } + + /** + * Creates a new reflection class. + * + * @param string $class + * + * @return \ReflectionClass + */ + public static function newReflectionClass($class) + { + return new \ReflectionClass(self::getRealClass($class)); + } + + /** + * Creates a new reflection object. + * + * @param object $object + * + * @return \ReflectionClass + */ + public static function newReflectionObject($object) + { + return self::newReflectionClass(self::getClass($object)); + } + + /** + * Given a class name and a proxy namespace returns the proxy name. + * + * @param string $className + * @param string $proxyNamespace + * + * @return string + */ + public static function generateProxyClassName($className, $proxyNamespace) + { + return rtrim($proxyNamespace, '\\') . '\\' . Proxy::MARKER . '\\' . ltrim($className, '\\'); + } +} diff --git a/vendor/doctrine/common/lib/Doctrine/Common/Util/Debug.php b/vendor/doctrine/common/lib/Doctrine/Common/Util/Debug.php new file mode 100644 index 000000000..b54622573 --- /dev/null +++ b/vendor/doctrine/common/lib/Doctrine/Common/Util/Debug.php @@ -0,0 +1,167 @@ + + * @author Jonathan Wage + * @author Roman Borschel + * @author Giorgio Sironi + * + * @deprecated The Debug class is deprecated, please use symfony/var-dumper instead. + */ +final class Debug +{ + /** + * Private constructor (prevents instantiation). + */ + private function __construct() + { + } + + /** + * Prints a dump of the public, protected and private properties of $var. + * + * @link https://xdebug.org/ + * + * @param mixed $var The variable to dump. + * @param integer $maxDepth The maximum nesting level for object properties. + * @param boolean $stripTags Whether output should strip HTML tags. + * @param boolean $echo Send the dumped value to the output buffer + * + * @return string + */ + public static function dump($var, $maxDepth = 2, $stripTags = true, $echo = true) + { + $html = ini_get('html_errors'); + + if ($html !== true) { + ini_set('html_errors', true); + } + + if (extension_loaded('xdebug')) { + ini_set('xdebug.var_display_max_depth', $maxDepth); + } + + $var = self::export($var, $maxDepth); + + ob_start(); + var_dump($var); + + $dump = ob_get_contents(); + + ob_end_clean(); + + $dumpText = ($stripTags ? strip_tags(html_entity_decode($dump)) : $dump); + + ini_set('html_errors', $html); + + if ($echo) { + echo $dumpText; + } + + return $dumpText; + } + + /** + * @param mixed $var + * @param int $maxDepth + * + * @return mixed + */ + public static function export($var, $maxDepth) + { + $return = null; + $isObj = is_object($var); + + if ($var instanceof Collection) { + $var = $var->toArray(); + } + + if ( ! $maxDepth) { + return is_object($var) ? get_class($var) + : (is_array($var) ? 'Array(' . count($var) . ')' : $var); + } + + if (is_array($var)) { + $return = []; + + foreach ($var as $k => $v) { + $return[$k] = self::export($v, $maxDepth - 1); + } + + return $return; + } + + if ( ! $isObj) { + return $var; + } + + $return = new \stdClass(); + if ($var instanceof \DateTimeInterface) { + $return->__CLASS__ = get_class($var); + $return->date = $var->format('c'); + $return->timezone = $var->getTimezone()->getName(); + + return $return; + } + + $return->__CLASS__ = ClassUtils::getClass($var); + + if ($var instanceof Proxy) { + $return->__IS_PROXY__ = true; + $return->__PROXY_INITIALIZED__ = $var->__isInitialized(); + } + + if ($var instanceof \ArrayObject || $var instanceof \ArrayIterator) { + $return->__STORAGE__ = self::export($var->getArrayCopy(), $maxDepth - 1); + } + + return self::fillReturnWithClassAttributes($var, $return, $maxDepth); + } + + /** + * Fill the $return variable with class attributes + * Based on obj2array function from {@see https://secure.php.net/manual/en/function.get-object-vars.php#47075} + * + * @param object $var + * @param \stdClass $return + * @param int $maxDepth + * + * @return mixed + */ + private static function fillReturnWithClassAttributes($var, \stdClass $return, $maxDepth) + { + $clone = (array) $var; + + foreach (array_keys($clone) as $key) { + $aux = explode("\0", $key); + $name = end($aux); + if ($aux[0] === '') { + $name .= ':' . ($aux[1] === '*' ? 'protected' : $aux[1] . ':private'); + } + $return->$name = self::export($clone[$key], $maxDepth - 1); + ; + } + + return $return; + } + + /** + * Returns a string representation of an object. + * + * @param object $obj + * + * @return string + */ + public static function toString($obj) + { + return method_exists($obj, '__toString') ? (string) $obj : get_class($obj) . '@' . spl_object_hash($obj); + } +} diff --git a/vendor/doctrine/common/lib/Doctrine/Common/Util/Inflector.php b/vendor/doctrine/common/lib/Doctrine/Common/Util/Inflector.php new file mode 100644 index 000000000..f41c54aa8 --- /dev/null +++ b/vendor/doctrine/common/lib/Doctrine/Common/Util/Inflector.php @@ -0,0 +1,19 @@ + + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + * + * @deprecated The Version class is deprecated, please refrain from checking the version of doctrine/common. + */ +class Version +{ + /** + * Current Doctrine Version. + */ + const VERSION = '2.12.0-DEV'; + + /** + * Compares a Doctrine version with the current one. + * + * @param string $version Doctrine version to compare. + * + * @return int -1 if older, 0 if it is the same, 1 if version passed as argument is newer. + */ + public static function compare($version) + { + $currentVersion = str_replace(' ', '', strtolower(self::VERSION)); + $version = str_replace(' ', '', $version); + + return version_compare($version, $currentVersion); + } +} diff --git a/vendor/doctrine/common/phpstan.neon.dist b/vendor/doctrine/common/phpstan.neon.dist new file mode 100644 index 000000000..56ba1e6bc --- /dev/null +++ b/vendor/doctrine/common/phpstan.neon.dist @@ -0,0 +1,46 @@ +parameters: + level: 3 + paths: + - %currentWorkingDirectory%/lib + - %currentWorkingDirectory%/tests + autoload_directories: + - %currentWorkingDirectory%/tests/Doctrine/Tests/Common/ClassLoaderTest + excludes_analyse: + - %currentWorkingDirectory%/lib/vendor/doctrine-build-common + - %currentWorkingDirectory%/tests/Doctrine/Tests/Common/Proxy/InvalidReturnTypeClass.php + - %currentWorkingDirectory%/tests/Doctrine/Tests/Common/Proxy/InvalidTypeHintClass.php + - %currentWorkingDirectory%/tests/Doctrine/Tests/Common/Proxy/SerializedClass.php + - %currentWorkingDirectory%/tests/Doctrine/Tests/Common/Proxy/VariadicTypeHintClass.php + - %currentWorkingDirectory%/tests/Doctrine/Tests/Common/Proxy/ProxyLogicTypedPropertiesTest.php + - %currentWorkingDirectory%/tests/Doctrine/Tests/Common/Proxy/LazyLoadableObjectWithTypedProperties.php + - %currentWorkingDirectory%/tests/Doctrine/Tests/Common/Proxy/generated + ignoreErrors: + - '#Access to an undefined property Doctrine\\Common\\Proxy\\Proxy::\$publicField#' + - + message: '#^Result of method Doctrine\\Tests\\Common\\Proxy\\LazyLoadableObjectWithVoid::(adding|incrementing)AndReturningVoid\(\) \(void\) is used\.$#' + path: '%currentWorkingDirectory%/tests/Doctrine/Tests/Common/Proxy/ProxyLogicVoidReturnTypeTest.php' + - + message: '#^Property Doctrine\\Tests\\Common\\Proxy\\ProxyLogicTest::\$initializerCallbackMock \(callable&PHPUnit\\Framework\\MockObject\\MockObject\) does not accept PHPUnit\\Framework\\MockObject\\MockObject&stdClass\.$#' + path: '%currentWorkingDirectory%/tests/Doctrine/Tests/Common/Proxy/ProxyLogicTest.php' + - + message: '#^Call to an undefined method PHPUnit\\Framework\\MockObject\\MockObject(&stdClass)?::(load|getName)\(\)\.$#' + path: '%currentWorkingDirectory%/tests/Doctrine/Tests/Common/Proxy/ProxyLogicTest.php' + - + message: '#^Access to an undefined property Doctrine\\Common\\Proxy\\Proxy&Doctrine\\Tests\\Common\\Proxy\\LazyLoadableObject::\$non_existing_property\.$#' + path: '%currentWorkingDirectory%/tests/Doctrine/Tests/Common/Proxy/ProxyLogicTest.php' + - + message: '#^Instantiated class Doctrine\\Tests\\Common\\ProxyProxy\\__CG__\\Doctrine\\Tests\\Common\\Proxy\\.* not found.$#' + path: '%currentWorkingDirectory%/tests/Doctrine/Tests/Common/Proxy/ProxyLogicTest.php' + - + message: '#^Instantiated class Doctrine\\Tests\\Common\\ProxyProxy\\__CG__\\Doctrine\\Tests\\Common\\Proxy\\.* not found.$#' + path: '%currentWorkingDirectory%/tests/Doctrine/Tests/Common/Proxy/ProxyLogicVoidReturnTypeTest.php' + - + message: '#^Property Doctrine\\Tests\\Common\\Proxy\\ProxyLogicVoidReturnTypeTest::\$initializerCallbackMock \(callable&PHPUnit\\Framework\\MockObject\\MockObject\) does not accept PHPUnit\\Framework\\MockObject\\MockObject&stdClass\.$#' + path: '%currentWorkingDirectory%/tests/Doctrine/Tests/Common/Proxy/ProxyLogicVoidReturnTypeTest.php' + - + message: '#^Method Doctrine\\Tests\\Common\\Proxy\\MagicIssetClassWithInteger::__isset\(\) should return bool but returns int\.$#' + path: '%currentWorkingDirectory%/tests/Doctrine/Tests/Common/Proxy/MagicIssetClassWithInteger.php' + +includes: + - vendor/phpstan/phpstan-phpunit/extension.neon + - vendor/phpstan/phpstan-phpunit/rules.neon diff --git a/vendor/doctrine/event-manager/.doctrine-project.json b/vendor/doctrine/event-manager/.doctrine-project.json new file mode 100644 index 000000000..f6feb43c4 --- /dev/null +++ b/vendor/doctrine/event-manager/.doctrine-project.json @@ -0,0 +1,18 @@ +{ + "active": true, + "name": "Event Manager", + "slug": "event-manager", + "docsSlug": "doctrine-event-manager", + "versions": [ + { + "name": "1.0", + "branchName": "master", + "slug": "latest", + "current": true, + "aliases": [ + "current", + "stable" + ] + } + ] +} diff --git a/vendor/doctrine/event-manager/LICENSE b/vendor/doctrine/event-manager/LICENSE new file mode 100644 index 000000000..8c38cc1bc --- /dev/null +++ b/vendor/doctrine/event-manager/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2006-2015 Doctrine Project + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/doctrine/event-manager/README.md b/vendor/doctrine/event-manager/README.md new file mode 100644 index 000000000..3aee6b6f0 --- /dev/null +++ b/vendor/doctrine/event-manager/README.md @@ -0,0 +1,13 @@ +# Doctrine Event Manager + +[![Build Status](https://travis-ci.org/doctrine/event-manager.svg)](https://travis-ci.org/doctrine/event-manager) +[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/doctrine/event-manager/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/doctrine/event-manager/?branch=master) +[![Code Coverage](https://scrutinizer-ci.com/g/doctrine/event-manager/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/doctrine/event-manager/?branch=master) + +The Doctrine Event Manager is a library that provides a simple event system. + +## More resources: + +* [Website](https://www.doctrine-project.org/) +* [Documentation](https://www.doctrine-project.org/projects/doctrine-event-manager/en/latest/) +* [Downloads](https://github.com/doctrine/event-manager/releases) diff --git a/vendor/doctrine/event-manager/composer.json b/vendor/doctrine/event-manager/composer.json new file mode 100644 index 000000000..3f026fe8b --- /dev/null +++ b/vendor/doctrine/event-manager/composer.json @@ -0,0 +1,50 @@ +{ + "name": "doctrine/event-manager", + "type": "library", + "description": "The Doctrine Event Manager is a simple PHP event system that was built to be used with the various Doctrine projects.", + "keywords": [ + "events", + "event", + "event dispatcher", + "event manager", + "event system" + ], + "homepage": "https://www.doctrine-project.org/projects/event-manager.html", + "license": "MIT", + "authors": [ + {"name": "Guilherme Blanco", "email": "guilhermeblanco@gmail.com"}, + {"name": "Roman Borschel", "email": "roman@code-factory.org"}, + {"name": "Benjamin Eberlei", "email": "kontakt@beberlei.de"}, + {"name": "Jonathan Wage", "email": "jonwage@gmail.com"}, + {"name": "Johannes Schmitt", "email": "schmittjoh@gmail.com"}, + {"name": "Marco Pivetta", "email": "ocramius@gmail.com"} + ], + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^6.0", + "phpunit/phpunit": "^7.0" + }, + "conflict": { + "doctrine/common": "<2.9@dev" + }, + "config": { + "sort-packages": true + }, + "autoload": { + "psr-4": { + "Doctrine\\Common\\": "lib/Doctrine/Common" + } + }, + "autoload-dev": { + "psr-4": { + "Doctrine\\Tests\\": "tests/Doctrine/Tests" + } + }, + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + } +} diff --git a/vendor/doctrine/event-manager/lib/Doctrine/Common/EventArgs.php b/vendor/doctrine/event-manager/lib/Doctrine/Common/EventArgs.php new file mode 100644 index 000000000..9b9cc0565 --- /dev/null +++ b/vendor/doctrine/event-manager/lib/Doctrine/Common/EventArgs.php @@ -0,0 +1,45 @@ + => + * + * @var object[][] + */ + private $_listeners = []; + + /** + * Dispatches an event to all registered listeners. + * + * @param string $eventName The name of the event to dispatch. The name of the event is + * the name of the method that is invoked on listeners. + * @param EventArgs|null $eventArgs The event arguments to pass to the event handlers/listeners. + * If not supplied, the single empty EventArgs instance is used. + * + * @return void + */ + public function dispatchEvent($eventName, ?EventArgs $eventArgs = null) + { + if (! isset($this->_listeners[$eventName])) { + return; + } + + $eventArgs = $eventArgs ?? EventArgs::getEmptyInstance(); + + foreach ($this->_listeners[$eventName] as $listener) { + $listener->$eventName($eventArgs); + } + } + + /** + * Gets the listeners of a specific event or all listeners. + * + * @param string|null $event The name of the event. + * + * @return object[]|object[][] The event listeners for the specified event, or all event listeners. + */ + public function getListeners($event = null) + { + return $event ? $this->_listeners[$event] : $this->_listeners; + } + + /** + * Checks whether an event has any registered listeners. + * + * @param string $event + * + * @return bool TRUE if the specified event has any listeners, FALSE otherwise. + */ + public function hasListeners($event) + { + return ! empty($this->_listeners[$event]); + } + + /** + * Adds an event listener that listens on the specified events. + * + * @param string|string[] $events The event(s) to listen on. + * @param object $listener The listener object. + * + * @return void + */ + public function addEventListener($events, $listener) + { + // Picks the hash code related to that listener + $hash = spl_object_hash($listener); + + foreach ((array) $events as $event) { + // Overrides listener if a previous one was associated already + // Prevents duplicate listeners on same event (same instance only) + $this->_listeners[$event][$hash] = $listener; + } + } + + /** + * Removes an event listener from the specified events. + * + * @param string|string[] $events + * @param object $listener + * + * @return void + */ + public function removeEventListener($events, $listener) + { + // Picks the hash code related to that listener + $hash = spl_object_hash($listener); + + foreach ((array) $events as $event) { + unset($this->_listeners[$event][$hash]); + } + } + + /** + * Adds an EventSubscriber. The subscriber is asked for all the events it is + * interested in and added as a listener for these events. + * + * @param EventSubscriber $subscriber The subscriber. + * + * @return void + */ + public function addEventSubscriber(EventSubscriber $subscriber) + { + $this->addEventListener($subscriber->getSubscribedEvents(), $subscriber); + } + + /** + * Removes an EventSubscriber. The subscriber is asked for all the events it is + * interested in and removed as a listener for these events. + * + * @param EventSubscriber $subscriber The subscriber. + * + * @return void + */ + public function removeEventSubscriber(EventSubscriber $subscriber) + { + $this->removeEventListener($subscriber->getSubscribedEvents(), $subscriber); + } +} diff --git a/vendor/doctrine/event-manager/lib/Doctrine/Common/EventSubscriber.php b/vendor/doctrine/event-manager/lib/Doctrine/Common/EventSubscriber.php new file mode 100644 index 000000000..7d5e2ea3f --- /dev/null +++ b/vendor/doctrine/event-manager/lib/Doctrine/Common/EventSubscriber.php @@ -0,0 +1,21 @@ +build(); + +By default it will create an English inflector. If you want to use another language, just pass the language +you want to create an inflector for to the ``createForLanguage()`` method: + +.. code-block:: php + + use Doctrine\Inflector\InflectorFactory; + use Doctrine\Inflector\Language; + + $inflector = InflectorFactory::createForLanguage(Language::SPANISH)->build(); + +The supported languages are as follows: + +- ``Language::ENGLISH`` +- ``Language::FRENCH`` +- ``Language::NORWEGIAN_BOKMAL`` +- ``Language::PORTUGUESE`` +- ``Language::SPANISH`` +- ``Language::TURKISH`` + +If you want to manually construct the inflector instead of using a factory, you can do so like this: + +.. code-block:: php + + use Doctrine\Inflector\CachedWordInflector; + use Doctrine\Inflector\RulesetInflector; + use Doctrine\Inflector\Rules\English; + + $inflector = new Inflector( + new CachedWordInflector(new RulesetInflector( + English\Rules::getSingularRuleset() + )), + new CachedWordInflector(new RulesetInflector( + English\Rules::getPluralRuleset() + )) + ); + +Adding Languages +---------------- + +If you are interested in adding support for your language, take a look at the other languages defined in the +``Doctrine\Inflector\Rules`` namespace and the tests located in ``Doctrine\Tests\Inflector\Rules``. You can copy +one of the languages and update the rules for your language. + +Once you have done this, send a pull request to the ``doctrine/inflector`` repository with the additions. + +Custom Setup +============ + +If you want to setup custom singular and plural rules, you can configure these in the factory: + +.. code-block:: php + + use Doctrine\Inflector\InflectorFactory; + use Doctrine\Inflector\Rules\Pattern; + use Doctrine\Inflector\Rules\Patterns; + use Doctrine\Inflector\Rules\Ruleset; + use Doctrine\Inflector\Rules\Substitution; + use Doctrine\Inflector\Rules\Substitutions; + use Doctrine\Inflector\Rules\Transformation; + use Doctrine\Inflector\Rules\Transformations; + use Doctrine\Inflector\Rules\Word; + + $inflector = InflectorFactory::create() + ->withSingularRules( + new Ruleset( + new Transformations( + new Transformation(new Pattern('/^(bil)er$/i'), '\1'), + new Transformation(new Pattern('/^(inflec|contribu)tors$/i'), '\1ta') + ), + new Patterns(new Pattern('singulars')), + new Substitutions(new Substitution(new Word('spins'), new Word('spinor'))) + ) + ) + ->withPluralRules( + new Ruleset( + new Transformations( + new Transformation(new Pattern('^(bil)er$'), '\1'), + new Transformation(new Pattern('^(inflec|contribu)tors$'), '\1ta') + ), + new Patterns(new Pattern('noflect'), new Pattern('abtuse')), + new Substitutions( + new Substitution(new Word('amaze'), new Word('amazable')), + new Substitution(new Word('phone'), new Word('phonezes')) + ) + ) + ) + ->build(); + +No operation inflector +---------------------- + +The ``Doctrine\Inflector\NoopWordInflector`` may be used to configure an inflector that doesn't perform any operation for +pluralization and/or singularization. If will simply return the input as output. + +This is an implementation of the `Null Object design pattern `_. + +.. code-block:: php + + use Doctrine\Inflector\Inflector; + use Doctrine\Inflector\NoopWordInflector; + + $inflector = new Inflector(new NoopWordInflector(), new NoopWordInflector()); + +Tableize +======== + +Converts ``ModelName`` to ``model_name``: + +.. code-block:: php + + echo $inflector->tableize('ModelName'); // model_name + +Classify +======== + +Converts ``model_name`` to ``ModelName``: + +.. code-block:: php + + echo $inflector->classify('model_name'); // ModelName + +Camelize +======== + +This method uses `Classify`_ and then converts the first character to lowercase: + +.. code-block:: php + + echo $inflector->camelize('model_name'); // modelName + +Capitalize +========== + +Takes a string and capitalizes all of the words, like PHP's built-in +``ucwords`` function. This extends that behavior, however, by allowing the +word delimiters to be configured, rather than only separating on +whitespace. + +Here is an example: + +.. code-block:: php + + $string = 'top-o-the-morning to all_of_you!'; + + echo $inflector->capitalize($string); // Top-O-The-Morning To All_of_you! + + echo $inflector->capitalize($string, '-_ '); // Top-O-The-Morning To All_Of_You! + +Pluralize +========= + +Returns a word in plural form. + +.. code-block:: php + + echo $inflector->pluralize('browser'); // browsers + +Singularize +=========== + +Returns a word in singular form. + +.. code-block:: php + + echo $inflector->singularize('browsers'); // browser + +Urlize +====== + +Generate a URL friendly string from a string of text: + +.. code-block:: php + + echo $inflector->urlize('My first blog post'); // my-first-blog-post + +Unaccent +======== + +You can unaccent a string of text using the ``unaccent()`` method: + +.. code-block:: php + + echo $inflector->unaccent('año'); // ano + +Legacy API +========== + +The API present in Inflector 1.x is still available, but will be deprecated in a future release and dropped for 3.0. +Support for languages other than English is available in the 2.0 API only. + +Acknowledgements +================ + +The language rules in this library have been adapted from several different sources, including but not limited to: + +- `Ruby On Rails Inflector `_ +- `ICanBoogie Inflector `_ +- `CakePHP Inflector `_ diff --git a/vendor/doctrine/inflector/lib/Doctrine/Common/Inflector/Inflector.php b/vendor/doctrine/inflector/lib/Doctrine/Common/Inflector/Inflector.php new file mode 100644 index 000000000..d00e56516 --- /dev/null +++ b/vendor/doctrine/inflector/lib/Doctrine/Common/Inflector/Inflector.php @@ -0,0 +1,281 @@ +. + */ + +namespace Doctrine\Common\Inflector; + +use Doctrine\Inflector\Inflector as InflectorObject; +use Doctrine\Inflector\InflectorFactory; +use Doctrine\Inflector\LanguageInflectorFactory; +use Doctrine\Inflector\Rules\Pattern; +use Doctrine\Inflector\Rules\Patterns; +use Doctrine\Inflector\Rules\Ruleset; +use Doctrine\Inflector\Rules\Substitution; +use Doctrine\Inflector\Rules\Substitutions; +use Doctrine\Inflector\Rules\Transformation; +use Doctrine\Inflector\Rules\Transformations; +use Doctrine\Inflector\Rules\Word; +use InvalidArgumentException; +use function array_keys; +use function array_map; +use function array_unshift; +use function array_values; +use function sprintf; +use function trigger_error; +use const E_USER_DEPRECATED; + +/** + * @deprecated + */ +class Inflector +{ + /** + * @var LanguageInflectorFactory|null + */ + private static $factory; + + /** @var InflectorObject|null */ + private static $instance; + + private static function getInstance() : InflectorObject + { + if (self::$factory === null) { + self::$factory = self::createFactory(); + } + + if (self::$instance === null) { + self::$instance = self::$factory->build(); + } + + return self::$instance; + } + + private static function createFactory() : LanguageInflectorFactory + { + return InflectorFactory::create(); + } + + /** + * Converts a word into the format for a Doctrine table name. Converts 'ModelName' to 'model_name'. + * + * @deprecated + */ + public static function tableize(string $word) : string + { + @trigger_error(sprintf('The "%s" method is deprecated and will be dropped in doctrine/inflector 2.0. Please update to the new Inflector API.', __METHOD__), E_USER_DEPRECATED); + + return self::getInstance()->tableize($word); + } + + /** + * Converts a word into the format for a Doctrine class name. Converts 'table_name' to 'TableName'. + */ + public static function classify(string $word) : string + { + @trigger_error(sprintf('The "%s" method is deprecated and will be dropped in doctrine/inflector 2.0. Please update to the new Inflector API.', __METHOD__), E_USER_DEPRECATED); + + return self::getInstance()->classify($word); + } + + /** + * Camelizes a word. This uses the classify() method and turns the first character to lowercase. + * + * @deprecated + */ + public static function camelize(string $word) : string + { + @trigger_error(sprintf('The "%s" method is deprecated and will be dropped in doctrine/inflector 2.0. Please update to the new Inflector API.', __METHOD__), E_USER_DEPRECATED); + + return self::getInstance()->camelize($word); + } + + /** + * Uppercases words with configurable delimiters between words. + * + * Takes a string and capitalizes all of the words, like PHP's built-in + * ucwords function. This extends that behavior, however, by allowing the + * word delimiters to be configured, rather than only separating on + * whitespace. + * + * Here is an example: + * + * + * + * + * @param string $string The string to operate on. + * @param string $delimiters A list of word separators. + * + * @return string The string with all delimiter-separated words capitalized. + * + * @deprecated + */ + public static function ucwords(string $string, string $delimiters = " \n\t\r\0\x0B-") : string + { + @trigger_error(sprintf('The "%s" method is deprecated and will be dropped in doctrine/inflector 2.0. Please use the "ucwords" function instead.', __METHOD__), E_USER_DEPRECATED); + + return ucwords($string, $delimiters); + } + + /** + * Clears Inflectors inflected value caches, and resets the inflection + * rules to the initial values. + * + * @deprecated + */ + public static function reset() : void + { + @trigger_error(sprintf('The "%s" method is deprecated and will be dropped in doctrine/inflector 2.0. Please update to the new Inflector API.', __METHOD__), E_USER_DEPRECATED); + + self::$factory = null; + self::$instance = null; + } + + /** + * Adds custom inflection $rules, of either 'plural' or 'singular' $type. + * + * ### Usage: + * + * {{{ + * Inflector::rules('plural', array('/^(inflect)or$/i' => '\1ables')); + * Inflector::rules('plural', array( + * 'rules' => array('/^(inflect)ors$/i' => '\1ables'), + * 'uninflected' => array('dontinflectme'), + * 'irregular' => array('red' => 'redlings') + * )); + * }}} + * + * @param string $type The type of inflection, either 'plural' or 'singular' + * @param array|iterable $rules An array of rules to be added. + * @param boolean $reset If true, will unset default inflections for all + * new rules that are being defined in $rules. + * + * @return void + * + * @deprecated + */ + public static function rules(string $type, iterable $rules, bool $reset = false) : void + { + @trigger_error(sprintf('The "%s" method is deprecated and will be dropped in doctrine/inflector 2.0. Please update to the new Inflector API.', __METHOD__), E_USER_DEPRECATED); + + if (self::$factory === null) { + self::$factory = self::createFactory(); + } + + self::$instance = null; + + switch ($type) { + case 'singular': + self::$factory->withSingularRules(self::buildRuleset($rules), $reset); + break; + case 'plural': + self::$factory->withPluralRules(self::buildRuleset($rules), $reset); + break; + default: + throw new InvalidArgumentException(sprintf('Cannot define custom inflection rules for type "%s".', $type)); + } + } + + private static function buildRuleset(iterable $rules) : Ruleset + { + $regular = []; + $irregular = []; + $uninflected = []; + + foreach ($rules as $rule => $pattern) { + if ( ! is_array($pattern)) { + $regular[$rule] = $pattern; + + continue; + } + + switch ($rule) { + case 'uninflected': + $uninflected = $pattern; + break; + case 'irregular': + $irregular = $pattern; + break; + case 'rules': + $regular = $pattern; + break; + } + } + + return new Ruleset( + new Transformations(...array_map( + static function (string $pattern, string $replacement) : Transformation { + return new Transformation(new Pattern($pattern), $replacement); + }, + array_keys($regular), + array_values($regular) + )), + new Patterns(...array_map( + static function (string $pattern) : Pattern { + return new Pattern($pattern); + }, + $uninflected + )), + new Substitutions(...array_map( + static function (string $word, string $to) : Substitution { + return new Substitution(new Word($word), new Word($to)); + }, + array_keys($irregular), + array_values($irregular) + )) + ); + } + + /** + * Returns a word in plural form. + * + * @param string $word The word in singular form. + * + * @return string The word in plural form. + * + * @deprecated + */ + public static function pluralize(string $word) : string + { + @trigger_error(sprintf('The "%s" method is deprecated and will be dropped in doctrine/inflector 2.0. Please update to the new Inflector API.', __METHOD__), E_USER_DEPRECATED); + + return self::getInstance()->pluralize($word); + } + + /** + * Returns a word in singular form. + * + * @param string $word The word in plural form. + * + * @return string The word in singular form. + * + * @deprecated + */ + public static function singularize(string $word) : string + { + @trigger_error(sprintf('The "%s" method is deprecated and will be dropped in doctrine/inflector 2.0. Please update to the new Inflector API.', __METHOD__), E_USER_DEPRECATED); + + return self::getInstance()->singularize($word); + } +} diff --git a/vendor/doctrine/inflector/lib/Doctrine/Inflector/CachedWordInflector.php b/vendor/doctrine/inflector/lib/Doctrine/Inflector/CachedWordInflector.php new file mode 100644 index 000000000..b59ac46c1 --- /dev/null +++ b/vendor/doctrine/inflector/lib/Doctrine/Inflector/CachedWordInflector.php @@ -0,0 +1,24 @@ +wordInflector = $wordInflector; + } + + public function inflect(string $word) : string + { + return $this->cache[$word] ?? $this->cache[$word] = $this->wordInflector->inflect($word); + } +} diff --git a/vendor/doctrine/inflector/lib/Doctrine/Inflector/GenericLanguageInflectorFactory.php b/vendor/doctrine/inflector/lib/Doctrine/Inflector/GenericLanguageInflectorFactory.php new file mode 100644 index 000000000..1b15061a0 --- /dev/null +++ b/vendor/doctrine/inflector/lib/Doctrine/Inflector/GenericLanguageInflectorFactory.php @@ -0,0 +1,65 @@ +singularRulesets[] = $this->getSingularRuleset(); + $this->pluralRulesets[] = $this->getPluralRuleset(); + } + + final public function build() : Inflector + { + return new Inflector( + new CachedWordInflector(new RulesetInflector( + ...$this->singularRulesets + )), + new CachedWordInflector(new RulesetInflector( + ...$this->pluralRulesets + )) + ); + } + + final public function withSingularRules(?Ruleset $singularRules, bool $reset = false) : LanguageInflectorFactory + { + if ($reset) { + $this->singularRulesets = []; + } + + if ($singularRules instanceof Ruleset) { + array_unshift($this->singularRulesets, $singularRules); + } + + return $this; + } + + final public function withPluralRules(?Ruleset $pluralRules, bool $reset = false) : LanguageInflectorFactory + { + if ($reset) { + $this->pluralRulesets = []; + } + + if ($pluralRules instanceof Ruleset) { + array_unshift($this->pluralRulesets, $pluralRules); + } + + return $this; + } + + abstract protected function getSingularRuleset() : Ruleset; + + abstract protected function getPluralRuleset() : Ruleset; +} diff --git a/vendor/doctrine/inflector/lib/Doctrine/Inflector/Inflector.php b/vendor/doctrine/inflector/lib/Doctrine/Inflector/Inflector.php new file mode 100644 index 000000000..241191850 --- /dev/null +++ b/vendor/doctrine/inflector/lib/Doctrine/Inflector/Inflector.php @@ -0,0 +1,506 @@ + 'A', + 'Á' => 'A', + 'Â' => 'A', + 'Ã' => 'A', + 'Ä' => 'Ae', + 'Æ' => 'Ae', + 'Å' => 'Aa', + 'æ' => 'a', + 'Ç' => 'C', + 'È' => 'E', + 'É' => 'E', + 'Ê' => 'E', + 'Ë' => 'E', + 'Ì' => 'I', + 'Í' => 'I', + 'Î' => 'I', + 'Ï' => 'I', + 'Ñ' => 'N', + 'Ò' => 'O', + 'Ó' => 'O', + 'Ô' => 'O', + 'Õ' => 'O', + 'Ö' => 'Oe', + 'Ù' => 'U', + 'Ú' => 'U', + 'Û' => 'U', + 'Ü' => 'Ue', + 'Ý' => 'Y', + 'ß' => 'ss', + 'à' => 'a', + 'á' => 'a', + 'â' => 'a', + 'ã' => 'a', + 'ä' => 'ae', + 'å' => 'aa', + 'ç' => 'c', + 'è' => 'e', + 'é' => 'e', + 'ê' => 'e', + 'ë' => 'e', + 'ì' => 'i', + 'í' => 'i', + 'î' => 'i', + 'ï' => 'i', + 'ñ' => 'n', + 'ò' => 'o', + 'ó' => 'o', + 'ô' => 'o', + 'õ' => 'o', + 'ö' => 'oe', + 'ù' => 'u', + 'ú' => 'u', + 'û' => 'u', + 'ü' => 'ue', + 'ý' => 'y', + 'ÿ' => 'y', + 'Ā' => 'A', + 'ā' => 'a', + 'Ă' => 'A', + 'ă' => 'a', + 'Ą' => 'A', + 'ą' => 'a', + 'Ć' => 'C', + 'ć' => 'c', + 'Ĉ' => 'C', + 'ĉ' => 'c', + 'Ċ' => 'C', + 'ċ' => 'c', + 'Č' => 'C', + 'č' => 'c', + 'Ď' => 'D', + 'ď' => 'd', + 'Đ' => 'D', + 'đ' => 'd', + 'Ē' => 'E', + 'ē' => 'e', + 'Ĕ' => 'E', + 'ĕ' => 'e', + 'Ė' => 'E', + 'ė' => 'e', + 'Ę' => 'E', + 'ę' => 'e', + 'Ě' => 'E', + 'ě' => 'e', + 'Ĝ' => 'G', + 'ĝ' => 'g', + 'Ğ' => 'G', + 'ğ' => 'g', + 'Ġ' => 'G', + 'ġ' => 'g', + 'Ģ' => 'G', + 'ģ' => 'g', + 'Ĥ' => 'H', + 'ĥ' => 'h', + 'Ħ' => 'H', + 'ħ' => 'h', + 'Ĩ' => 'I', + 'ĩ' => 'i', + 'Ī' => 'I', + 'ī' => 'i', + 'Ĭ' => 'I', + 'ĭ' => 'i', + 'Į' => 'I', + 'į' => 'i', + 'İ' => 'I', + 'ı' => 'i', + 'IJ' => 'IJ', + 'ij' => 'ij', + 'Ĵ' => 'J', + 'ĵ' => 'j', + 'Ķ' => 'K', + 'ķ' => 'k', + 'ĸ' => 'k', + 'Ĺ' => 'L', + 'ĺ' => 'l', + 'Ļ' => 'L', + 'ļ' => 'l', + 'Ľ' => 'L', + 'ľ' => 'l', + 'Ŀ' => 'L', + 'ŀ' => 'l', + 'Ł' => 'L', + 'ł' => 'l', + 'Ń' => 'N', + 'ń' => 'n', + 'Ņ' => 'N', + 'ņ' => 'n', + 'Ň' => 'N', + 'ň' => 'n', + 'ʼn' => 'N', + 'Ŋ' => 'n', + 'ŋ' => 'N', + 'Ō' => 'O', + 'ō' => 'o', + 'Ŏ' => 'O', + 'ŏ' => 'o', + 'Ő' => 'O', + 'ő' => 'o', + 'Œ' => 'OE', + 'œ' => 'oe', + 'Ø' => 'O', + 'ø' => 'o', + 'Ŕ' => 'R', + 'ŕ' => 'r', + 'Ŗ' => 'R', + 'ŗ' => 'r', + 'Ř' => 'R', + 'ř' => 'r', + 'Ś' => 'S', + 'ś' => 's', + 'Ŝ' => 'S', + 'ŝ' => 's', + 'Ş' => 'S', + 'ş' => 's', + 'Š' => 'S', + 'š' => 's', + 'Ţ' => 'T', + 'ţ' => 't', + 'Ť' => 'T', + 'ť' => 't', + 'Ŧ' => 'T', + 'ŧ' => 't', + 'Ũ' => 'U', + 'ũ' => 'u', + 'Ū' => 'U', + 'ū' => 'u', + 'Ŭ' => 'U', + 'ŭ' => 'u', + 'Ů' => 'U', + 'ů' => 'u', + 'Ű' => 'U', + 'ű' => 'u', + 'Ų' => 'U', + 'ų' => 'u', + 'Ŵ' => 'W', + 'ŵ' => 'w', + 'Ŷ' => 'Y', + 'ŷ' => 'y', + 'Ÿ' => 'Y', + 'Ź' => 'Z', + 'ź' => 'z', + 'Ż' => 'Z', + 'ż' => 'z', + 'Ž' => 'Z', + 'ž' => 'z', + 'ſ' => 's', + '€' => 'E', + '£' => '', + ]; + + /** @var WordInflector */ + private $singularizer; + + /** @var WordInflector */ + private $pluralizer; + + public function __construct(WordInflector $singularizer, WordInflector $pluralizer) + { + $this->singularizer = $singularizer; + $this->pluralizer = $pluralizer; + } + + /** + * Converts a word into the format for a Doctrine table name. Converts 'ModelName' to 'model_name'. + */ + public function tableize(string $word) : string + { + $tableized = preg_replace('~(?<=\\w)([A-Z])~u', '_$1', $word); + + if ($tableized === null) { + throw new RuntimeException(sprintf( + 'preg_replace returned null for value "%s"', + $word + )); + } + + return mb_strtolower($tableized); + } + + /** + * Converts a word into the format for a Doctrine class name. Converts 'table_name' to 'TableName'. + */ + public function classify(string $word) : string + { + return str_replace([' ', '_', '-'], '', ucwords($word, ' _-')); + } + + /** + * Camelizes a word. This uses the classify() method and turns the first character to lowercase. + */ + public function camelize(string $word) : string + { + return lcfirst($this->classify($word)); + } + + /** + * Uppercases words with configurable delimiters between words. + * + * Takes a string and capitalizes all of the words, like PHP's built-in + * ucwords function. This extends that behavior, however, by allowing the + * word delimiters to be configured, rather than only separating on + * whitespace. + * + * Here is an example: + * + * capitalize($string); + * // Top-O-The-Morning To All_of_you! + * + * echo $inflector->capitalize($string, '-_ '); + * // Top-O-The-Morning To All_Of_You! + * ?> + * + * + * @param string $string The string to operate on. + * @param string $delimiters A list of word separators. + * + * @return string The string with all delimiter-separated words capitalized. + */ + public function capitalize(string $string, string $delimiters = " \n\t\r\0\x0B-") : string + { + return ucwords($string, $delimiters); + } + + /** + * Checks if the given string seems like it has utf8 characters in it. + * + * @param string $string The string to check for utf8 characters in. + */ + public function seemsUtf8(string $string) : bool + { + for ($i = 0; $i < strlen($string); $i++) { + if (ord($string[$i]) < 0x80) { + continue; // 0bbbbbbb + } + + if ((ord($string[$i]) & 0xE0) === 0xC0) { + $n = 1; // 110bbbbb + } elseif ((ord($string[$i]) & 0xF0) === 0xE0) { + $n = 2; // 1110bbbb + } elseif ((ord($string[$i]) & 0xF8) === 0xF0) { + $n = 3; // 11110bbb + } elseif ((ord($string[$i]) & 0xFC) === 0xF8) { + $n = 4; // 111110bb + } elseif ((ord($string[$i]) & 0xFE) === 0xFC) { + $n = 5; // 1111110b + } else { + return false; // Does not match any model + } + + for ($j = 0; $j < $n; $j++) { // n bytes matching 10bbbbbb follow ? + if (++$i === strlen($string) || ((ord($string[$i]) & 0xC0) !== 0x80)) { + return false; + } + } + } + + return true; + } + + /** + * Remove any illegal characters, accents, etc. + * + * @param string $string String to unaccent + * + * @return string Unaccented string + */ + public function unaccent(string $string) : string + { + if (preg_match('/[\x80-\xff]/', $string) === false) { + return $string; + } + + if ($this->seemsUtf8($string)) { + $string = strtr($string, self::ACCENTED_CHARACTERS); + } else { + $characters = []; + + // Assume ISO-8859-1 if not UTF-8 + $characters['in'] = + chr(128) + . chr(131) + . chr(138) + . chr(142) + . chr(154) + . chr(158) + . chr(159) + . chr(162) + . chr(165) + . chr(181) + . chr(192) + . chr(193) + . chr(194) + . chr(195) + . chr(196) + . chr(197) + . chr(199) + . chr(200) + . chr(201) + . chr(202) + . chr(203) + . chr(204) + . chr(205) + . chr(206) + . chr(207) + . chr(209) + . chr(210) + . chr(211) + . chr(212) + . chr(213) + . chr(214) + . chr(216) + . chr(217) + . chr(218) + . chr(219) + . chr(220) + . chr(221) + . chr(224) + . chr(225) + . chr(226) + . chr(227) + . chr(228) + . chr(229) + . chr(231) + . chr(232) + . chr(233) + . chr(234) + . chr(235) + . chr(236) + . chr(237) + . chr(238) + . chr(239) + . chr(241) + . chr(242) + . chr(243) + . chr(244) + . chr(245) + . chr(246) + . chr(248) + . chr(249) + . chr(250) + . chr(251) + . chr(252) + . chr(253) + . chr(255); + + $characters['out'] = 'EfSZszYcYuAAAAAACEEEEIIIINOOOOOOUUUUYaaaaaaceeeeiiiinoooooouuuuyy'; + + $string = strtr($string, $characters['in'], $characters['out']); + + $doubleChars = []; + + $doubleChars['in'] = [ + chr(140), + chr(156), + chr(198), + chr(208), + chr(222), + chr(223), + chr(230), + chr(240), + chr(254), + ]; + + $doubleChars['out'] = ['OE', 'oe', 'AE', 'DH', 'TH', 'ss', 'ae', 'dh', 'th']; + + $string = str_replace($doubleChars['in'], $doubleChars['out'], $string); + } + + return $string; + } + + /** + * Convert any passed string to a url friendly string. + * Converts 'My first blog post' to 'my-first-blog-post' + * + * @param string $string String to urlize. + * + * @return string Urlized string. + */ + public function urlize(string $string) : string + { + // Remove all non url friendly characters with the unaccent function + $unaccented = $this->unaccent($string); + + if (function_exists('mb_strtolower')) { + $lowered = mb_strtolower($unaccented); + } else { + $lowered = strtolower($unaccented); + } + + $replacements = [ + '/\W/' => ' ', + '/([A-Z]+)([A-Z][a-z])/' => '\1_\2', + '/([a-z\d])([A-Z])/' => '\1_\2', + '/[^A-Z^a-z^0-9^\/]+/' => '-', + ]; + + $urlized = $lowered; + + foreach ($replacements as $pattern => $replacement) { + $replaced = preg_replace($pattern, $replacement, $urlized); + + if ($replaced === null) { + throw new RuntimeException(sprintf( + 'preg_replace returned null for value "%s"', + $urlized + )); + } + + $urlized = $replaced; + } + + return trim($urlized, '-'); + } + + /** + * Returns a word in singular form. + * + * @param string $word The word in plural form. + * + * @return string The word in singular form. + */ + public function singularize(string $word) : string + { + return $this->singularizer->inflect($word); + } + + /** + * Returns a word in plural form. + * + * @param string $word The word in singular form. + * + * @return string The word in plural form. + */ + public function pluralize(string $word) : string + { + return $this->pluralizer->inflect($word); + } +} diff --git a/vendor/doctrine/inflector/lib/Doctrine/Inflector/InflectorFactory.php b/vendor/doctrine/inflector/lib/Doctrine/Inflector/InflectorFactory.php new file mode 100644 index 000000000..44bba5d7e --- /dev/null +++ b/vendor/doctrine/inflector/lib/Doctrine/Inflector/InflectorFactory.php @@ -0,0 +1,45 @@ +getFlippedSubstitutions() + ); + } + + public static function getPluralRuleset() : Ruleset + { + return new Ruleset( + new Transformations(...Inflectible::getPlural()), + new Patterns(...Uninflected::getPlural()), + new Substitutions(...Inflectible::getIrregular()) + ); + } +} diff --git a/vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/English/Uninflected.php b/vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/English/Uninflected.php new file mode 100644 index 000000000..be37a97ba --- /dev/null +++ b/vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/English/Uninflected.php @@ -0,0 +1,193 @@ +getFlippedSubstitutions() + ); + } + + public static function getPluralRuleset() : Ruleset + { + return new Ruleset( + new Transformations(...Inflectible::getPlural()), + new Patterns(...Uninflected::getPlural()), + new Substitutions(...Inflectible::getIrregular()) + ); + } +} diff --git a/vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/French/Uninflected.php b/vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/French/Uninflected.php new file mode 100644 index 000000000..2fdc020a4 --- /dev/null +++ b/vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/French/Uninflected.php @@ -0,0 +1,34 @@ +getFlippedSubstitutions() + ); + } + + public static function getPluralRuleset() : Ruleset + { + return new Ruleset( + new Transformations(...Inflectible::getPlural()), + new Patterns(...Uninflected::getPlural()), + new Substitutions(...Inflectible::getIrregular()) + ); + } +} diff --git a/vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/NorwegianBokmal/Uninflected.php b/vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/NorwegianBokmal/Uninflected.php new file mode 100644 index 000000000..c6b9fc792 --- /dev/null +++ b/vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/NorwegianBokmal/Uninflected.php @@ -0,0 +1,36 @@ +pattern = $pattern; + + if (isset($this->pattern[0]) && $this->pattern[0] === '/') { + $this->regex = $this->pattern; + } else { + $this->regex = '/' . $this->pattern . '/i'; + } + } + + public function getPattern() : string + { + return $this->pattern; + } + + public function getRegex() : string + { + return $this->regex; + } + + public function matches(string $word) : bool + { + return preg_match($this->getRegex(), $word) === 1; + } +} diff --git a/vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/Patterns.php b/vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/Patterns.php new file mode 100644 index 000000000..a71f1ed20 --- /dev/null +++ b/vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/Patterns.php @@ -0,0 +1,34 @@ +patterns = $patterns; + + $patterns = array_map(static function (Pattern $pattern) : string { + return $pattern->getPattern(); + }, $this->patterns); + + $this->regex = '/^(?:' . implode('|', $patterns) . ')$/i'; + } + + public function matches(string $word) : bool + { + return preg_match($this->regex, $word, $regs) === 1; + } +} diff --git a/vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/Portuguese/Inflectible.php b/vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/Portuguese/Inflectible.php new file mode 100644 index 000000000..155055f7a --- /dev/null +++ b/vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/Portuguese/Inflectible.php @@ -0,0 +1,104 @@ +getFlippedSubstitutions() + ); + } + + public static function getPluralRuleset() : Ruleset + { + return new Ruleset( + new Transformations(...Inflectible::getPlural()), + new Patterns(...Uninflected::getPlural()), + new Substitutions(...Inflectible::getIrregular()) + ); + } +} diff --git a/vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/Portuguese/Uninflected.php b/vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/Portuguese/Uninflected.php new file mode 100644 index 000000000..52360c456 --- /dev/null +++ b/vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/Portuguese/Uninflected.php @@ -0,0 +1,38 @@ +regular = $regular; + $this->uninflected = $uninflected; + $this->irregular = $irregular; + } + + public function getRegular() : Transformations + { + return $this->regular; + } + + public function getUninflected() : Patterns + { + return $this->uninflected; + } + + public function getIrregular() : Substitutions + { + return $this->irregular; + } +} diff --git a/vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/Spanish/Inflectible.php b/vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/Spanish/Inflectible.php new file mode 100644 index 000000000..6cace86a9 --- /dev/null +++ b/vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/Spanish/Inflectible.php @@ -0,0 +1,53 @@ +getFlippedSubstitutions() + ); + } + + public static function getPluralRuleset() : Ruleset + { + return new Ruleset( + new Transformations(...Inflectible::getPlural()), + new Patterns(...Uninflected::getPlural()), + new Substitutions(...Inflectible::getIrregular()) + ); + } +} diff --git a/vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/Spanish/Uninflected.php b/vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/Spanish/Uninflected.php new file mode 100644 index 000000000..b13281e8d --- /dev/null +++ b/vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/Spanish/Uninflected.php @@ -0,0 +1,36 @@ +from = $from; + $this->to = $to; + } + + public function getFrom() : Word + { + return $this->from; + } + + public function getTo() : Word + { + return $this->to; + } +} diff --git a/vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/Substitutions.php b/vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/Substitutions.php new file mode 100644 index 000000000..24cc34a8e --- /dev/null +++ b/vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/Substitutions.php @@ -0,0 +1,56 @@ +substitutions[$substitution->getFrom()->getWord()] = $substitution; + } + } + + public function getFlippedSubstitutions() : Substitutions + { + $substitutions = []; + + foreach ($this->substitutions as $substitution) { + $substitutions[] = new Substitution( + $substitution->getTo(), + $substitution->getFrom() + ); + } + + return new Substitutions(...$substitutions); + } + + public function inflect(string $word) : string + { + $lowerWord = strtolower($word); + + if (isset($this->substitutions[$lowerWord])) { + $firstLetterUppercase = $lowerWord[0] !== $word[0]; + + $toWord = $this->substitutions[$lowerWord]->getTo()->getWord(); + + if ($firstLetterUppercase) { + return strtoupper($toWord[0]) . substr($toWord, 1); + } + + return $toWord; + } + + return $word; + } +} diff --git a/vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/Transformation.php b/vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/Transformation.php new file mode 100644 index 000000000..84ef08b8d --- /dev/null +++ b/vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/Transformation.php @@ -0,0 +1,38 @@ +pattern = $pattern; + $this->replacement = $replacement; + } + + public function getPattern() : Pattern + { + return $this->pattern; + } + + public function getReplacement() : string + { + return $this->replacement; + } + + public function inflect(string $word) : string + { + return (string) preg_replace($this->pattern->getRegex(), $this->replacement, $word); + } +} diff --git a/vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/Transformations.php b/vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/Transformations.php new file mode 100644 index 000000000..9f4724e50 --- /dev/null +++ b/vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/Transformations.php @@ -0,0 +1,29 @@ +transformations = $transformations; + } + + public function inflect(string $word) : string + { + foreach ($this->transformations as $transformation) { + if ($transformation->getPattern()->matches($word)) { + return $transformation->inflect($word); + } + } + + return $word; + } +} diff --git a/vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/Turkish/Inflectible.php b/vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/Turkish/Inflectible.php new file mode 100644 index 000000000..74900cb99 --- /dev/null +++ b/vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/Turkish/Inflectible.php @@ -0,0 +1,40 @@ +getFlippedSubstitutions() + ); + } + + public static function getPluralRuleset() : Ruleset + { + return new Ruleset( + new Transformations(...Inflectible::getPlural()), + new Patterns(...Uninflected::getPlural()), + new Substitutions(...Inflectible::getIrregular()) + ); + } +} diff --git a/vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/Turkish/Uninflected.php b/vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/Turkish/Uninflected.php new file mode 100644 index 000000000..c95ccbf9b --- /dev/null +++ b/vendor/doctrine/inflector/lib/Doctrine/Inflector/Rules/Turkish/Uninflected.php @@ -0,0 +1,36 @@ +word = $word; + } + + public function getWord() : string + { + return $this->word; + } +} diff --git a/vendor/doctrine/inflector/lib/Doctrine/Inflector/RulesetInflector.php b/vendor/doctrine/inflector/lib/Doctrine/Inflector/RulesetInflector.php new file mode 100644 index 000000000..0e3a5ebd6 --- /dev/null +++ b/vendor/doctrine/inflector/lib/Doctrine/Inflector/RulesetInflector.php @@ -0,0 +1,55 @@ +rulesets = array_merge([$ruleset], $rulesets); + } + + public function inflect(string $word) : string + { + if ($word === '') { + return ''; + } + + foreach ($this->rulesets as $ruleset) { + if ($ruleset->getUninflected()->matches($word)) { + return $word; + } + + $inflected = $ruleset->getIrregular()->inflect($word); + + if ($inflected !== $word) { + return $inflected; + } + + $inflected = $ruleset->getRegular()->inflect($word); + + if ($inflected !== $word) { + return $inflected; + } + } + + return $word; + } +} diff --git a/vendor/doctrine/inflector/lib/Doctrine/Inflector/WordInflector.php b/vendor/doctrine/inflector/lib/Doctrine/Inflector/WordInflector.php new file mode 100644 index 000000000..f25705580 --- /dev/null +++ b/vendor/doctrine/inflector/lib/Doctrine/Inflector/WordInflector.php @@ -0,0 +1,10 @@ +getId(); // method exists through __call + */ +abstract class PersistentObject implements ObjectManagerAware +{ + /** @var ObjectManager|null */ + private static $objectManager = null; + + /** @var ClassMetadata|null */ + private $cm = null; + + /** + * Sets the object manager responsible for all persistent object base classes. + * + * @return void + */ + public static function setObjectManager(?ObjectManager $objectManager = null) + { + self::$objectManager = $objectManager; + } + + /** + * @return ObjectManager|null + */ + public static function getObjectManager() + { + return self::$objectManager; + } + + /** + * Injects the Doctrine Object Manager. + * + * @return void + * + * @throws RuntimeException + */ + public function injectObjectManager(ObjectManager $objectManager, ClassMetadata $classMetadata) + { + if ($objectManager !== self::$objectManager) { + throw new RuntimeException('Trying to use PersistentObject with different ObjectManager instances. ' . + 'Was PersistentObject::setObjectManager() called?'); + } + + $this->cm = $classMetadata; + } + + /** + * Sets a persistent fields value. + * + * @param string $field + * @param mixed[] $args + * + * @return void + * + * @throws BadMethodCallException When no persistent field exists by that name. + * @throws InvalidArgumentException When the wrong target object type is passed to an association. + */ + private function set($field, $args) + { + if ($this->cm->hasField($field) && ! $this->cm->isIdentifier($field)) { + $this->$field = $args[0]; + } elseif ($this->cm->hasAssociation($field) && $this->cm->isSingleValuedAssociation($field)) { + $targetClass = $this->cm->getAssociationTargetClass($field); + if (! ($args[0] instanceof $targetClass) && $args[0] !== null) { + throw new InvalidArgumentException("Expected persistent object of type '" . $targetClass . "'"); + } + $this->$field = $args[0]; + $this->completeOwningSide($field, $targetClass, $args[0]); + } else { + throw new BadMethodCallException("no field with name '" . $field . "' exists on '" . $this->cm->getName() . "'"); + } + } + + /** + * Gets a persistent field value. + * + * @param string $field + * + * @return mixed + * + * @throws BadMethodCallException When no persistent field exists by that name. + */ + private function get($field) + { + if ($this->cm->hasField($field) || $this->cm->hasAssociation($field)) { + return $this->$field; + } + + throw new BadMethodCallException("no field with name '" . $field . "' exists on '" . $this->cm->getName() . "'"); + } + + /** + * If this is an inverse side association, completes the owning side. + * + * @param string $field + * @param ClassMetadata $targetClass + * @param object $targetObject + * + * @return void + */ + private function completeOwningSide($field, $targetClass, $targetObject) + { + // add this object on the owning side as well, for obvious infinite recursion + // reasons this is only done when called on the inverse side. + if (! $this->cm->isAssociationInverseSide($field)) { + return; + } + + $mappedByField = $this->cm->getAssociationMappedByTargetField($field); + $targetMetadata = self::$objectManager->getClassMetadata($targetClass); + + $setter = ($targetMetadata->isCollectionValuedAssociation($mappedByField) ? 'add' : 'set') . $mappedByField; + $targetObject->$setter($this); + } + + /** + * Adds an object to a collection. + * + * @param string $field + * @param mixed[] $args + * + * @return void + * + * @throws BadMethodCallException + * @throws InvalidArgumentException + */ + private function add($field, $args) + { + if (! $this->cm->hasAssociation($field) || ! $this->cm->isCollectionValuedAssociation($field)) { + throw new BadMethodCallException('There is no method add' . $field . '() on ' . $this->cm->getName()); + } + + $targetClass = $this->cm->getAssociationTargetClass($field); + if (! ($args[0] instanceof $targetClass)) { + throw new InvalidArgumentException("Expected persistent object of type '" . $targetClass . "'"); + } + if (! ($this->$field instanceof Collection)) { + $this->$field = new ArrayCollection($this->$field ?: []); + } + $this->$field->add($args[0]); + $this->completeOwningSide($field, $targetClass, $args[0]); + } + + /** + * Initializes Doctrine Metadata for this class. + * + * @return void + * + * @throws RuntimeException + */ + private function initializeDoctrine() + { + if ($this->cm !== null) { + return; + } + + if (! self::$objectManager) { + throw new RuntimeException('No runtime object manager set. Call PersistentObject#setObjectManager().'); + } + + $this->cm = self::$objectManager->getClassMetadata(static::class); + } + + /** + * Magic methods. + * + * @param string $method + * @param mixed[] $args + * + * @return mixed + * + * @throws BadMethodCallException + */ + public function __call($method, $args) + { + $this->initializeDoctrine(); + + $command = substr($method, 0, 3); + $field = lcfirst(substr($method, 3)); + if ($command === 'set') { + $this->set($field, $args); + } elseif ($command === 'get') { + return $this->get($field); + } elseif ($command === 'add') { + $this->add($field, $args); + } else { + throw new BadMethodCallException('There is no method ' . $method . ' on ' . $this->cm->getName()); + } + } +} + +class_exists(\Doctrine\Common\Persistence\PersistentObject::class); diff --git a/vendor/doctrine/persistence/lib/Doctrine/Common/Persistence/Proxy.php b/vendor/doctrine/persistence/lib/Doctrine/Common/Persistence/Proxy.php new file mode 100644 index 000000000..c872c450e --- /dev/null +++ b/vendor/doctrine/persistence/lib/Doctrine/Common/Persistence/Proxy.php @@ -0,0 +1,16 @@ +name = $name; + $this->connections = $connections; + $this->managers = $managers; + $this->defaultConnection = $defaultConnection; + $this->defaultManager = $defaultManager; + $this->proxyInterfaceName = $proxyInterfaceName; + } + + /** + * Fetches/creates the given services. + * + * A service in this context is connection or a manager instance. + * + * @param string $name The name of the service. + * + * @return ObjectManager The instance of the given service. + */ + abstract protected function getService($name); + + /** + * Resets the given services. + * + * A service in this context is connection or a manager instance. + * + * @param string $name The name of the service. + * + * @return void + */ + abstract protected function resetService($name); + + /** + * Gets the name of the registry. + * + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * {@inheritdoc} + */ + public function getConnection($name = null) + { + if ($name === null) { + $name = $this->defaultConnection; + } + + if (! isset($this->connections[$name])) { + throw new InvalidArgumentException(sprintf('Doctrine %s Connection named "%s" does not exist.', $this->name, $name)); + } + + return $this->getService($this->connections[$name]); + } + + /** + * {@inheritdoc} + */ + public function getConnectionNames() + { + return $this->connections; + } + + /** + * {@inheritdoc} + */ + public function getConnections() + { + $connections = []; + foreach ($this->connections as $name => $id) { + $connections[$name] = $this->getService($id); + } + + return $connections; + } + + /** + * {@inheritdoc} + */ + public function getDefaultConnectionName() + { + return $this->defaultConnection; + } + + /** + * {@inheritdoc} + */ + public function getDefaultManagerName() + { + return $this->defaultManager; + } + + /** + * {@inheritdoc} + * + * @throws InvalidArgumentException + */ + public function getManager($name = null) + { + if ($name === null) { + $name = $this->defaultManager; + } + + if (! isset($this->managers[$name])) { + throw new InvalidArgumentException(sprintf('Doctrine %s Manager named "%s" does not exist.', $this->name, $name)); + } + + return $this->getService($this->managers[$name]); + } + + /** + * {@inheritdoc} + */ + public function getManagerForClass($class) + { + // Check for namespace alias + if (strpos($class, ':') !== false) { + [$namespaceAlias, $simpleClassName] = explode(':', $class, 2); + $class = $this->getAliasNamespace($namespaceAlias) . '\\' . $simpleClassName; + } + + $proxyClass = new ReflectionClass($class); + + if ($proxyClass->implementsInterface($this->proxyInterfaceName)) { + $parentClass = $proxyClass->getParentClass(); + + if (! $parentClass) { + return null; + } + + $class = $parentClass->getName(); + } + + foreach ($this->managers as $id) { + $manager = $this->getService($id); + + if (! $manager->getMetadataFactory()->isTransient($class)) { + return $manager; + } + } + } + + /** + * {@inheritdoc} + */ + public function getManagerNames() + { + return $this->managers; + } + + /** + * {@inheritdoc} + */ + public function getManagers() + { + $dms = []; + foreach ($this->managers as $name => $id) { + $dms[$name] = $this->getService($id); + } + + return $dms; + } + + /** + * {@inheritdoc} + */ + public function getRepository($persistentObjectName, $persistentManagerName = null) + { + return $this + ->selectManager($persistentObjectName, $persistentManagerName) + ->getRepository($persistentObjectName); + } + + /** + * {@inheritdoc} + */ + public function resetManager($name = null) + { + if ($name === null) { + $name = $this->defaultManager; + } + + if (! isset($this->managers[$name])) { + throw new InvalidArgumentException(sprintf('Doctrine %s Manager named "%s" does not exist.', $this->name, $name)); + } + + // force the creation of a new document manager + // if the current one is closed + $this->resetService($this->managers[$name]); + + return $this->getManager($name); + } + + private function selectManager(string $persistentObjectName, ?string $persistentManagerName = null) : ObjectManager + { + if ($persistentManagerName !== null) { + return $this->getManager($persistentManagerName); + } + + return $this->getManagerForClass($persistentObjectName) ?? $this->getManager(); + } +} + +class_exists(\Doctrine\Common\Persistence\AbstractManagerRegistry::class); +interface_exists(ObjectManager::class); diff --git a/vendor/doctrine/persistence/lib/Doctrine/Persistence/ConnectionRegistry.php b/vendor/doctrine/persistence/lib/Doctrine/Persistence/ConnectionRegistry.php new file mode 100644 index 000000000..a822c7193 --- /dev/null +++ b/vendor/doctrine/persistence/lib/Doctrine/Persistence/ConnectionRegistry.php @@ -0,0 +1,43 @@ +object = $object; + $this->objectManager = $objectManager; + } + + /** + * Retrieves the associated entity. + * + * @deprecated + * + * @return object + */ + public function getEntity() + { + return $this->object; + } + + /** + * Retrieves the associated object. + * + * @return object + */ + public function getObject() + { + return $this->object; + } + + /** + * Retrieves the associated ObjectManager. + * + * @return ObjectManager + */ + public function getObjectManager() + { + return $this->objectManager; + } +} + +class_exists(\Doctrine\Common\Persistence\Event\LifecycleEventArgs::class); diff --git a/vendor/doctrine/persistence/lib/Doctrine/Persistence/Event/LoadClassMetadataEventArgs.php b/vendor/doctrine/persistence/lib/Doctrine/Persistence/Event/LoadClassMetadataEventArgs.php new file mode 100644 index 000000000..31aae4a0d --- /dev/null +++ b/vendor/doctrine/persistence/lib/Doctrine/Persistence/Event/LoadClassMetadataEventArgs.php @@ -0,0 +1,48 @@ +classMetadata = $classMetadata; + $this->objectManager = $objectManager; + } + + /** + * Retrieves the associated ClassMetadata. + * + * @return ClassMetadata + */ + public function getClassMetadata() + { + return $this->classMetadata; + } + + /** + * Retrieves the associated ObjectManager. + * + * @return ObjectManager + */ + public function getObjectManager() + { + return $this->objectManager; + } +} + +class_exists(\Doctrine\Common\Persistence\Event\LoadClassMetadataEventArgs::class); diff --git a/vendor/doctrine/persistence/lib/Doctrine/Persistence/Event/ManagerEventArgs.php b/vendor/doctrine/persistence/lib/Doctrine/Persistence/Event/ManagerEventArgs.php new file mode 100644 index 000000000..66dd90350 --- /dev/null +++ b/vendor/doctrine/persistence/lib/Doctrine/Persistence/Event/ManagerEventArgs.php @@ -0,0 +1,33 @@ +objectManager = $objectManager; + } + + /** + * Retrieves the associated ObjectManager. + * + * @return ObjectManager + */ + public function getObjectManager() + { + return $this->objectManager; + } +} + +class_exists(\Doctrine\Common\Persistence\Event\ManagerEventArgs::class); diff --git a/vendor/doctrine/persistence/lib/Doctrine/Persistence/Event/OnClearEventArgs.php b/vendor/doctrine/persistence/lib/Doctrine/Persistence/Event/OnClearEventArgs.php new file mode 100644 index 000000000..cc263c133 --- /dev/null +++ b/vendor/doctrine/persistence/lib/Doctrine/Persistence/Event/OnClearEventArgs.php @@ -0,0 +1,61 @@ +objectManager = $objectManager; + $this->entityClass = $entityClass; + } + + /** + * Retrieves the associated ObjectManager. + * + * @return ObjectManager + */ + public function getObjectManager() + { + return $this->objectManager; + } + + /** + * Returns the name of the entity class that is cleared, or null if all are cleared. + * + * @return string|null + */ + public function getEntityClass() + { + return $this->entityClass; + } + + /** + * Returns whether this event clears all entities. + * + * @return bool + */ + public function clearsAllEntities() + { + return $this->entityClass === null; + } +} + +class_exists(\Doctrine\Common\Persistence\Event\OnClearEventArgs::class); diff --git a/vendor/doctrine/persistence/lib/Doctrine/Persistence/Event/PreUpdateEventArgs.php b/vendor/doctrine/persistence/lib/Doctrine/Persistence/Event/PreUpdateEventArgs.php new file mode 100644 index 000000000..b9f837644 --- /dev/null +++ b/vendor/doctrine/persistence/lib/Doctrine/Persistence/Event/PreUpdateEventArgs.php @@ -0,0 +1,116 @@ +entityChangeSet = &$changeSet; + } + + /** + * Retrieves the entity changeset. + * + * @return mixed[][] + */ + public function getEntityChangeSet() + { + return $this->entityChangeSet; + } + + /** + * Checks if field has a changeset. + * + * @param string $field + * + * @return bool + */ + public function hasChangedField($field) + { + return isset($this->entityChangeSet[$field]); + } + + /** + * Gets the old value of the changeset of the changed field. + * + * @param string $field + * + * @return mixed + */ + public function getOldValue($field) + { + $this->assertValidField($field); + + return $this->entityChangeSet[$field][0]; + } + + /** + * Gets the new value of the changeset of the changed field. + * + * @param string $field + * + * @return mixed + */ + public function getNewValue($field) + { + $this->assertValidField($field); + + return $this->entityChangeSet[$field][1]; + } + + /** + * Sets the new value of this field. + * + * @param string $field + * @param mixed $value + * + * @return void + */ + public function setNewValue($field, $value) + { + $this->assertValidField($field); + + $this->entityChangeSet[$field][1] = $value; + } + + /** + * Asserts the field exists in changeset. + * + * @param string $field + * + * @return void + * + * @throws InvalidArgumentException + */ + private function assertValidField($field) + { + if (! isset($this->entityChangeSet[$field])) { + throw new InvalidArgumentException(sprintf( + 'Field "%s" is not a valid field of the entity "%s" in PreUpdateEventArgs.', + $field, + get_class($this->getObject()) + )); + } + } +} + +class_exists(\Doctrine\Common\Persistence\Event\PreUpdateEventArgs::class); diff --git a/vendor/doctrine/persistence/lib/Doctrine/Persistence/ManagerRegistry.php b/vendor/doctrine/persistence/lib/Doctrine/Persistence/ManagerRegistry.php new file mode 100644 index 000000000..b28664ce4 --- /dev/null +++ b/vendor/doctrine/persistence/lib/Doctrine/Persistence/ManagerRegistry.php @@ -0,0 +1,92 @@ +cacheDriver = $cacheDriver; + } + + /** + * Gets the cache driver used by the factory to cache ClassMetadata instances. + * + * @return Cache|null + */ + public function getCacheDriver() + { + return $this->cacheDriver; + } + + /** + * Returns an array of all the loaded metadata currently in memory. + * + * @return ClassMetadata[] + */ + public function getLoadedMetadata() + { + return $this->loadedMetadata; + } + + /** + * Forces the factory to load the metadata of all classes known to the underlying + * mapping driver. + * + * @return ClassMetadata[] The ClassMetadata instances of all mapped classes. + */ + public function getAllMetadata() + { + if (! $this->initialized) { + $this->initialize(); + } + + $driver = $this->getDriver(); + $metadata = []; + foreach ($driver->getAllClassNames() as $className) { + $metadata[] = $this->getMetadataFor($className); + } + + return $metadata; + } + + /** + * Lazy initialization of this stuff, especially the metadata driver, + * since these are not needed at all when a metadata cache is active. + * + * @return void + */ + abstract protected function initialize(); + + /** + * Gets the fully qualified class-name from the namespace alias. + * + * @param string $namespaceAlias + * @param string $simpleClassName + * + * @return string + */ + abstract protected function getFqcnFromAlias($namespaceAlias, $simpleClassName); + + /** + * Returns the mapping driver implementation. + * + * @return MappingDriver + */ + abstract protected function getDriver(); + + /** + * Wakes up reflection after ClassMetadata gets unserialized from cache. + * + * @return void + */ + abstract protected function wakeupReflection(ClassMetadata $class, ReflectionService $reflService); + + /** + * Initializes Reflection after ClassMetadata was constructed. + * + * @return void + */ + abstract protected function initializeReflection(ClassMetadata $class, ReflectionService $reflService); + + /** + * Checks whether the class metadata is an entity. + * + * This method should return false for mapped superclasses or embedded classes. + * + * @return bool + */ + abstract protected function isEntity(ClassMetadata $class); + + /** + * Gets the class metadata descriptor for a class. + * + * @param string $className The name of the class. + * + * @return ClassMetadata + * + * @throws ReflectionException + * @throws MappingException + */ + public function getMetadataFor($className) + { + if (isset($this->loadedMetadata[$className])) { + return $this->loadedMetadata[$className]; + } + + // Check for namespace alias + if (strpos($className, ':') !== false) { + [$namespaceAlias, $simpleClassName] = explode(':', $className, 2); + + $realClassName = $this->getFqcnFromAlias($namespaceAlias, $simpleClassName); + } else { + $realClassName = $this->getRealClass($className); + } + + if (isset($this->loadedMetadata[$realClassName])) { + // We do not have the alias name in the map, include it + return $this->loadedMetadata[$className] = $this->loadedMetadata[$realClassName]; + } + + $loadingException = null; + + try { + if ($this->cacheDriver) { + $cached = $this->cacheDriver->fetch($realClassName . $this->cacheSalt); + if ($cached instanceof ClassMetadata) { + $this->loadedMetadata[$realClassName] = $cached; + + $this->wakeupReflection($cached, $this->getReflectionService()); + } else { + foreach ($this->loadMetadata($realClassName) as $loadedClassName) { + $this->cacheDriver->save( + $loadedClassName . $this->cacheSalt, + $this->loadedMetadata[$loadedClassName] + ); + } + } + } else { + $this->loadMetadata($realClassName); + } + } catch (MappingException $loadingException) { + $fallbackMetadataResponse = $this->onNotFoundMetadata($realClassName); + + if (! $fallbackMetadataResponse) { + throw $loadingException; + } + + $this->loadedMetadata[$realClassName] = $fallbackMetadataResponse; + } + + if ($className !== $realClassName) { + // We do not have the alias name in the map, include it + $this->loadedMetadata[$className] = $this->loadedMetadata[$realClassName]; + } + + return $this->loadedMetadata[$className]; + } + + /** + * Checks whether the factory has the metadata for a class loaded already. + * + * @param string $className + * + * @return bool TRUE if the metadata of the class in question is already loaded, FALSE otherwise. + */ + public function hasMetadataFor($className) + { + return isset($this->loadedMetadata[$className]); + } + + /** + * Sets the metadata descriptor for a specific class. + * + * NOTE: This is only useful in very special cases, like when generating proxy classes. + * + * @param string $className + * @param ClassMetadata $class + * + * @return void + */ + public function setMetadataFor($className, $class) + { + $this->loadedMetadata[$className] = $class; + } + + /** + * Gets an array of parent classes for the given entity class. + * + * @param string $name + * + * @return string[] + */ + protected function getParentClasses($name) + { + // Collect parent classes, ignoring transient (not-mapped) classes. + $parentClasses = []; + + foreach (array_reverse($this->getReflectionService()->getParentClasses($name)) as $parentClass) { + if ($this->getDriver()->isTransient($parentClass)) { + continue; + } + + $parentClasses[] = $parentClass; + } + + return $parentClasses; + } + + /** + * Loads the metadata of the class in question and all it's ancestors whose metadata + * is still not loaded. + * + * Important: The class $name does not necessarily exist at this point here. + * Scenarios in a code-generation setup might have access to XML/YAML + * Mapping files without the actual PHP code existing here. That is why the + * {@see Doctrine\Common\Persistence\Mapping\ReflectionService} interface + * should be used for reflection. + * + * @param string $name The name of the class for which the metadata should get loaded. + * + * @return string[] + */ + protected function loadMetadata($name) + { + if (! $this->initialized) { + $this->initialize(); + } + + $loaded = []; + + $parentClasses = $this->getParentClasses($name); + $parentClasses[] = $name; + + // Move down the hierarchy of parent classes, starting from the topmost class + $parent = null; + $rootEntityFound = false; + $visited = []; + $reflService = $this->getReflectionService(); + foreach ($parentClasses as $className) { + if (isset($this->loadedMetadata[$className])) { + $parent = $this->loadedMetadata[$className]; + if ($this->isEntity($parent)) { + $rootEntityFound = true; + array_unshift($visited, $className); + } + continue; + } + + $class = $this->newClassMetadataInstance($className); + $this->initializeReflection($class, $reflService); + + $this->doLoadMetadata($class, $parent, $rootEntityFound, $visited); + + $this->loadedMetadata[$className] = $class; + + $parent = $class; + + if ($this->isEntity($class)) { + $rootEntityFound = true; + array_unshift($visited, $className); + } + + $this->wakeupReflection($class, $reflService); + + $loaded[] = $className; + } + + return $loaded; + } + + /** + * Provides a fallback hook for loading metadata when loading failed due to reflection/mapping exceptions + * + * Override this method to implement a fallback strategy for failed metadata loading + * + * @param string $className + * + * @return ClassMetadata|null + */ + protected function onNotFoundMetadata($className) + { + return null; + } + + /** + * Actually loads the metadata from the underlying metadata. + * + * @param ClassMetadata $class + * @param ClassMetadata|null $parent + * @param bool $rootEntityFound + * @param string[] $nonSuperclassParents All parent class names + * that are not marked as mapped superclasses. + * + * @return void + */ + abstract protected function doLoadMetadata($class, $parent, $rootEntityFound, array $nonSuperclassParents); + + /** + * Creates a new ClassMetadata instance for the given class name. + * + * @param string $className + * + * @return ClassMetadata + */ + abstract protected function newClassMetadataInstance($className); + + /** + * {@inheritDoc} + */ + public function isTransient($class) + { + if (! $this->initialized) { + $this->initialize(); + } + + // Check for namespace alias + if (strpos($class, ':') !== false) { + [$namespaceAlias, $simpleClassName] = explode(':', $class, 2); + $class = $this->getFqcnFromAlias($namespaceAlias, $simpleClassName); + } + + return $this->getDriver()->isTransient($class); + } + + /** + * Sets the reflectionService. + * + * @return void + */ + public function setReflectionService(ReflectionService $reflectionService) + { + $this->reflectionService = $reflectionService; + } + + /** + * Gets the reflection service associated with this metadata factory. + * + * @return ReflectionService + */ + public function getReflectionService() + { + if ($this->reflectionService === null) { + $this->reflectionService = new RuntimeReflectionService(); + } + + return $this->reflectionService; + } + + /** + * Gets the real class name of a class name that could be a proxy. + */ + private function getRealClass(string $class) : string + { + $pos = strrpos($class, '\\' . Proxy::MARKER . '\\'); + + if ($pos === false) { + return $class; + } + + return substr($class, $pos + Proxy::MARKER_LENGTH + 2); + } +} + +class_exists(\Doctrine\Common\Persistence\Mapping\AbstractClassMetadataFactory::class); +interface_exists(ClassMetadata::class); +interface_exists(ReflectionService::class); diff --git a/vendor/doctrine/persistence/lib/Doctrine/Persistence/Mapping/ClassMetadata.php b/vendor/doctrine/persistence/lib/Doctrine/Persistence/Mapping/ClassMetadata.php new file mode 100644 index 000000000..7b995d566 --- /dev/null +++ b/vendor/doctrine/persistence/lib/Doctrine/Persistence/Mapping/ClassMetadata.php @@ -0,0 +1,157 @@ +reader = $reader; + if (! $paths) { + return; + } + + $this->addPaths((array) $paths); + } + + /** + * Appends lookup paths to metadata driver. + * + * @param string[] $paths + * + * @return void + */ + public function addPaths(array $paths) + { + $this->paths = array_unique(array_merge($this->paths, $paths)); + } + + /** + * Retrieves the defined metadata lookup paths. + * + * @return string[] + */ + public function getPaths() + { + return $this->paths; + } + + /** + * Append exclude lookup paths to metadata driver. + * + * @param string[] $paths + */ + public function addExcludePaths(array $paths) + { + $this->excludePaths = array_unique(array_merge($this->excludePaths, $paths)); + } + + /** + * Retrieve the defined metadata lookup exclude paths. + * + * @return string[] + */ + public function getExcludePaths() + { + return $this->excludePaths; + } + + /** + * Retrieve the current annotation reader + * + * @return Reader + */ + public function getReader() + { + return $this->reader; + } + + /** + * Gets the file extension used to look for mapping files under. + * + * @return string + */ + public function getFileExtension() + { + return $this->fileExtension; + } + + /** + * Sets the file extension used to look for mapping files under. + * + * @param string $fileExtension The file extension to set. + * + * @return void + */ + public function setFileExtension($fileExtension) + { + $this->fileExtension = $fileExtension; + } + + /** + * Returns whether the class with the specified name is transient. Only non-transient + * classes, that is entities and mapped superclasses, should have their metadata loaded. + * + * A class is non-transient if it is annotated with an annotation + * from the {@see AnnotationDriver::entityAnnotationClasses}. + * + * @param string $className + * + * @return bool + */ + public function isTransient($className) + { + $classAnnotations = $this->reader->getClassAnnotations(new ReflectionClass($className)); + + foreach ($classAnnotations as $annot) { + if (isset($this->entityAnnotationClasses[get_class($annot)])) { + return false; + } + } + + return true; + } + + /** + * {@inheritDoc} + */ + public function getAllClassNames() + { + if ($this->classNames !== null) { + return $this->classNames; + } + + if (! $this->paths) { + throw MappingException::pathRequired(); + } + + $classes = []; + $includedFiles = []; + + foreach ($this->paths as $path) { + if (! is_dir($path)) { + throw MappingException::fileMappingDriversRequireConfiguredDirectoryPath($path); + } + + $iterator = new RegexIterator( + new RecursiveIteratorIterator( + new RecursiveDirectoryIterator($path, FilesystemIterator::SKIP_DOTS), + RecursiveIteratorIterator::LEAVES_ONLY + ), + '/^.+' . preg_quote($this->fileExtension) . '$/i', + RecursiveRegexIterator::GET_MATCH + ); + + foreach ($iterator as $file) { + $sourceFile = $file[0]; + + if (! preg_match('(^phar:)i', $sourceFile)) { + $sourceFile = realpath($sourceFile); + } + + foreach ($this->excludePaths as $excludePath) { + $exclude = str_replace('\\', '/', realpath($excludePath)); + $current = str_replace('\\', '/', $sourceFile); + + if (strpos($current, $exclude) !== false) { + continue 2; + } + } + + require_once $sourceFile; + + $includedFiles[] = $sourceFile; + } + } + + $declared = get_declared_classes(); + + foreach ($declared as $className) { + $rc = new ReflectionClass($className); + $sourceFile = $rc->getFileName(); + if (! in_array($sourceFile, $includedFiles) || $this->isTransient($className)) { + continue; + } + + $classes[] = $className; + } + + $this->classNames = $classes; + + return $classes; + } +} + +class_exists(\Doctrine\Common\Persistence\Mapping\Driver\AnnotationDriver::class); diff --git a/vendor/doctrine/persistence/lib/Doctrine/Persistence/Mapping/Driver/DefaultFileLocator.php b/vendor/doctrine/persistence/lib/Doctrine/Persistence/Mapping/Driver/DefaultFileLocator.php new file mode 100644 index 000000000..4f49bc2df --- /dev/null +++ b/vendor/doctrine/persistence/lib/Doctrine/Persistence/Mapping/Driver/DefaultFileLocator.php @@ -0,0 +1,164 @@ +addPaths((array) $paths); + $this->fileExtension = $fileExtension; + } + + /** + * Appends lookup paths to metadata driver. + * + * @param string[] $paths + * + * @return void + */ + public function addPaths(array $paths) + { + $this->paths = array_unique(array_merge($this->paths, $paths)); + } + + /** + * Retrieves the defined metadata lookup paths. + * + * @return string[] + */ + public function getPaths() + { + return $this->paths; + } + + /** + * Gets the file extension used to look for mapping files under. + * + * @return string|null + */ + public function getFileExtension() + { + return $this->fileExtension; + } + + /** + * Sets the file extension used to look for mapping files under. + * + * @param string|null $fileExtension The file extension to set. + * + * @return void + */ + public function setFileExtension($fileExtension) + { + $this->fileExtension = $fileExtension; + } + + /** + * {@inheritDoc} + */ + public function findMappingFile($className) + { + $fileName = str_replace('\\', '.', $className) . $this->fileExtension; + + // Check whether file exists + foreach ($this->paths as $path) { + if (is_file($path . DIRECTORY_SEPARATOR . $fileName)) { + return $path . DIRECTORY_SEPARATOR . $fileName; + } + } + + throw MappingException::mappingFileNotFound($className, $fileName); + } + + /** + * {@inheritDoc} + */ + public function getAllClassNames($globalBasename) + { + $classes = []; + + if ($this->paths) { + foreach ($this->paths as $path) { + if (! is_dir($path)) { + throw MappingException::fileMappingDriversRequireConfiguredDirectoryPath($path); + } + + $iterator = new RecursiveIteratorIterator( + new RecursiveDirectoryIterator($path), + RecursiveIteratorIterator::LEAVES_ONLY + ); + + foreach ($iterator as $file) { + $fileName = $file->getBasename($this->fileExtension); + + if ($fileName === $file->getBasename() || $fileName === $globalBasename) { + continue; + } + + // NOTE: All files found here means classes are not transient! + $classes[] = str_replace('.', '\\', $fileName); + } + } + } + + return $classes; + } + + /** + * {@inheritDoc} + */ + public function fileExists($className) + { + $fileName = str_replace('\\', '.', $className) . $this->fileExtension; + + // Check whether file exists + foreach ((array) $this->paths as $path) { + if (is_file($path . DIRECTORY_SEPARATOR . $fileName)) { + return true; + } + } + + return false; + } +} + +class_exists(\Doctrine\Common\Persistence\Mapping\Driver\DefaultFileLocator::class); diff --git a/vendor/doctrine/persistence/lib/Doctrine/Persistence/Mapping/Driver/FileDriver.php b/vendor/doctrine/persistence/lib/Doctrine/Persistence/Mapping/Driver/FileDriver.php new file mode 100644 index 000000000..c8c783a6e --- /dev/null +++ b/vendor/doctrine/persistence/lib/Doctrine/Persistence/Mapping/Driver/FileDriver.php @@ -0,0 +1,199 @@ +locator = $locator; + } else { + $this->locator = new DefaultFileLocator((array) $locator, $fileExtension); + } + } + + /** + * Sets the global basename. + * + * @param string $file + * + * @return void + */ + public function setGlobalBasename($file) + { + $this->globalBasename = $file; + } + + /** + * Retrieves the global basename. + * + * @return string|null + */ + public function getGlobalBasename() + { + return $this->globalBasename; + } + + /** + * Gets the element of schema meta data for the class from the mapping file. + * This will lazily load the mapping file if it is not loaded yet. + * + * @param string $className + * + * @return ClassMetadata The element of schema meta data. + * + * @throws MappingException + */ + public function getElement($className) + { + if ($this->classCache === null) { + $this->initialize(); + } + + if (isset($this->classCache[$className])) { + return $this->classCache[$className]; + } + + $result = $this->loadMappingFile($this->locator->findMappingFile($className)); + if (! isset($result[$className])) { + throw MappingException::invalidMappingFile($className, str_replace('\\', '.', $className) . $this->locator->getFileExtension()); + } + + $this->classCache[$className] = $result[$className]; + + return $result[$className]; + } + + /** + * {@inheritDoc} + */ + public function isTransient($className) + { + if ($this->classCache === null) { + $this->initialize(); + } + + if (isset($this->classCache[$className])) { + return false; + } + + return ! $this->locator->fileExists($className); + } + + /** + * {@inheritDoc} + */ + public function getAllClassNames() + { + if ($this->classCache === null) { + $this->initialize(); + } + + if (! $this->classCache) { + return (array) $this->locator->getAllClassNames($this->globalBasename); + } + + return array_unique(array_merge( + array_keys($this->classCache), + (array) $this->locator->getAllClassNames($this->globalBasename) + )); + } + + /** + * Loads a mapping file with the given name and returns a map + * from class/entity names to their corresponding file driver elements. + * + * @param string $file The mapping file to load. + * + * @return ClassMetadata[] + */ + abstract protected function loadMappingFile($file); + + /** + * Initializes the class cache from all the global files. + * + * Using this feature adds a substantial performance hit to file drivers as + * more metadata has to be loaded into memory than might actually be + * necessary. This may not be relevant to scenarios where caching of + * metadata is in place, however hits very hard in scenarios where no + * caching is used. + * + * @return void + */ + protected function initialize() + { + $this->classCache = []; + if ($this->globalBasename === null) { + return; + } + + foreach ($this->locator->getPaths() as $path) { + $file = $path . '/' . $this->globalBasename . $this->locator->getFileExtension(); + if (! is_file($file)) { + continue; + } + + $this->classCache = array_merge( + $this->classCache, + $this->loadMappingFile($file) + ); + } + } + + /** + * Retrieves the locator used to discover mapping files by className. + * + * @return FileLocator + */ + public function getLocator() + { + return $this->locator; + } + + /** + * Sets the locator used to discover mapping files by className. + */ + public function setLocator(FileLocator $locator) + { + $this->locator = $locator; + } +} + +class_exists(\Doctrine\Common\Persistence\Mapping\Driver\FileDriver::class); +interface_exists(FileLocator::class); diff --git a/vendor/doctrine/persistence/lib/Doctrine/Persistence/Mapping/Driver/FileLocator.php b/vendor/doctrine/persistence/lib/Doctrine/Persistence/Mapping/Driver/FileLocator.php new file mode 100644 index 000000000..b14baf244 --- /dev/null +++ b/vendor/doctrine/persistence/lib/Doctrine/Persistence/Mapping/Driver/FileLocator.php @@ -0,0 +1,57 @@ +defaultDriver; + } + + /** + * Set the default driver. + * + * @return void + */ + public function setDefaultDriver(MappingDriver $driver) + { + $this->defaultDriver = $driver; + } + + /** + * Adds a nested driver. + * + * @param string $namespace + * + * @return void + */ + public function addDriver(MappingDriver $nestedDriver, $namespace) + { + $this->drivers[$namespace] = $nestedDriver; + } + + /** + * Gets the array of nested drivers. + * + * @return MappingDriver[] $drivers + */ + public function getDrivers() + { + return $this->drivers; + } + + /** + * {@inheritDoc} + */ + public function loadMetadataForClass($className, ClassMetadata $metadata) + { + /** @var MappingDriver $driver */ + foreach ($this->drivers as $namespace => $driver) { + if (strpos($className, $namespace) === 0) { + $driver->loadMetadataForClass($className, $metadata); + + return; + } + } + + if ($this->defaultDriver !== null) { + $this->defaultDriver->loadMetadataForClass($className, $metadata); + + return; + } + + throw MappingException::classNotFoundInNamespaces($className, array_keys($this->drivers)); + } + + /** + * {@inheritDoc} + */ + public function getAllClassNames() + { + $classNames = []; + $driverClasses = []; + + /** @var MappingDriver $driver */ + foreach ($this->drivers as $namespace => $driver) { + $oid = spl_object_hash($driver); + + if (! isset($driverClasses[$oid])) { + $driverClasses[$oid] = $driver->getAllClassNames(); + } + + foreach ($driverClasses[$oid] as $className) { + if (strpos($className, $namespace) !== 0) { + continue; + } + + $classNames[$className] = true; + } + } + + if ($this->defaultDriver !== null) { + foreach ($this->defaultDriver->getAllClassNames() as $className) { + $classNames[$className] = true; + } + } + + return array_keys($classNames); + } + + /** + * {@inheritDoc} + */ + public function isTransient($className) + { + /** @var MappingDriver $driver */ + foreach ($this->drivers as $namespace => $driver) { + if (strpos($className, $namespace) === 0) { + return $driver->isTransient($className); + } + } + + if ($this->defaultDriver !== null) { + return $this->defaultDriver->isTransient($className); + } + + return true; + } +} + +class_exists(\Doctrine\Common\Persistence\Mapping\Driver\MappingDriverChain::class); +interface_exists(ClassMetadata::class); diff --git a/vendor/doctrine/persistence/lib/Doctrine/Persistence/Mapping/Driver/PHPDriver.php b/vendor/doctrine/persistence/lib/Doctrine/Persistence/Mapping/Driver/PHPDriver.php new file mode 100644 index 000000000..4f1d9488d --- /dev/null +++ b/vendor/doctrine/persistence/lib/Doctrine/Persistence/Mapping/Driver/PHPDriver.php @@ -0,0 +1,49 @@ +metadata = $metadata; + + $this->loadMappingFile($this->locator->findMappingFile($className)); + } + + /** + * {@inheritDoc} + */ + protected function loadMappingFile($file) + { + $metadata = $this->metadata; + include $file; + + return [$metadata->getName() => $metadata]; + } +} + +class_exists(\Doctrine\Common\Persistence\Mapping\Driver\PHPDriver::class); +interface_exists(ClassMetadata::class); diff --git a/vendor/doctrine/persistence/lib/Doctrine/Persistence/Mapping/Driver/StaticPHPDriver.php b/vendor/doctrine/persistence/lib/Doctrine/Persistence/Mapping/Driver/StaticPHPDriver.php new file mode 100644 index 000000000..848b3bd0a --- /dev/null +++ b/vendor/doctrine/persistence/lib/Doctrine/Persistence/Mapping/Driver/StaticPHPDriver.php @@ -0,0 +1,134 @@ +addPaths((array) $paths); + } + + /** + * Adds paths. + * + * @param string[] $paths + * + * @return void + */ + public function addPaths(array $paths) + { + $this->paths = array_unique(array_merge($this->paths, $paths)); + } + + /** + * {@inheritdoc} + */ + public function loadMetadataForClass($className, ClassMetadata $metadata) + { + $className::loadMetadata($metadata); + } + + /** + * {@inheritDoc} + * + * @todo Same code exists in AnnotationDriver, should we re-use it somehow or not worry about it? + */ + public function getAllClassNames() + { + if ($this->classNames !== null) { + return $this->classNames; + } + + if (! $this->paths) { + throw MappingException::pathRequired(); + } + + $classes = []; + $includedFiles = []; + + foreach ($this->paths as $path) { + if (! is_dir($path)) { + throw MappingException::fileMappingDriversRequireConfiguredDirectoryPath($path); + } + + $iterator = new RecursiveIteratorIterator( + new RecursiveDirectoryIterator($path), + RecursiveIteratorIterator::LEAVES_ONLY + ); + + foreach ($iterator as $file) { + if ($file->getBasename('.php') === $file->getBasename()) { + continue; + } + + $sourceFile = realpath($file->getPathName()); + require_once $sourceFile; + $includedFiles[] = $sourceFile; + } + } + + $declared = get_declared_classes(); + + foreach ($declared as $className) { + $rc = new ReflectionClass($className); + $sourceFile = $rc->getFileName(); + if (! in_array($sourceFile, $includedFiles) || $this->isTransient($className)) { + continue; + } + + $classes[] = $className; + } + + $this->classNames = $classes; + + return $classes; + } + + /** + * {@inheritdoc} + */ + public function isTransient($className) + { + return ! method_exists($className, 'loadMetadata'); + } +} + +class_exists(\Doctrine\Common\Persistence\Mapping\Driver\StaticPHPDriver::class); +interface_exists(ClassMetadata::class); diff --git a/vendor/doctrine/persistence/lib/Doctrine/Persistence/Mapping/Driver/SymfonyFileLocator.php b/vendor/doctrine/persistence/lib/Doctrine/Persistence/Mapping/Driver/SymfonyFileLocator.php new file mode 100644 index 000000000..3124c6662 --- /dev/null +++ b/vendor/doctrine/persistence/lib/Doctrine/Persistence/Mapping/Driver/SymfonyFileLocator.php @@ -0,0 +1,233 @@ +addNamespacePrefixes($prefixes); + $this->fileExtension = $fileExtension; + + if (empty($nsSeparator)) { + throw new InvalidArgumentException('Namespace separator should not be empty'); + } + + $this->nsSeparator = (string) $nsSeparator; + } + + /** + * Adds Namespace Prefixes. + * + * @param string[] $prefixes + * + * @return void + */ + public function addNamespacePrefixes(array $prefixes) + { + $this->prefixes = array_merge($this->prefixes, $prefixes); + $this->paths = array_merge($this->paths, array_keys($prefixes)); + } + + /** + * Gets Namespace Prefixes. + * + * @return string[] + */ + public function getNamespacePrefixes() + { + return $this->prefixes; + } + + /** + * {@inheritDoc} + */ + public function getPaths() + { + return $this->paths; + } + + /** + * {@inheritDoc} + */ + public function getFileExtension() + { + return $this->fileExtension; + } + + /** + * Sets the file extension used to look for mapping files under. + * + * @param string $fileExtension The file extension to set. + * + * @return void + */ + public function setFileExtension($fileExtension) + { + $this->fileExtension = $fileExtension; + } + + /** + * {@inheritDoc} + */ + public function fileExists($className) + { + $defaultFileName = str_replace('\\', $this->nsSeparator, $className) . $this->fileExtension; + foreach ($this->paths as $path) { + if (! isset($this->prefixes[$path])) { + // global namespace class + if (is_file($path . DIRECTORY_SEPARATOR . $defaultFileName)) { + return true; + } + + continue; + } + + $prefix = $this->prefixes[$path]; + + if (strpos($className, $prefix . '\\') !== 0) { + continue; + } + + $filename = $path . '/' . strtr(substr($className, strlen($prefix) + 1), '\\', $this->nsSeparator) . $this->fileExtension; + if (is_file($filename)) { + return true; + } + } + + return false; + } + + /** + * {@inheritDoc} + */ + public function getAllClassNames($globalBasename = null) + { + $classes = []; + + if ($this->paths) { + foreach ((array) $this->paths as $path) { + if (! is_dir($path)) { + throw MappingException::fileMappingDriversRequireConfiguredDirectoryPath($path); + } + + $iterator = new RecursiveIteratorIterator( + new RecursiveDirectoryIterator($path), + RecursiveIteratorIterator::LEAVES_ONLY + ); + + foreach ($iterator as $file) { + $fileName = $file->getBasename($this->fileExtension); + + if ($fileName === $file->getBasename() || $fileName === $globalBasename) { + continue; + } + + // NOTE: All files found here means classes are not transient! + if (isset($this->prefixes[$path])) { + // Calculate namespace suffix for given prefix as a relative path from basepath to file path + $nsSuffix = strtr( + substr(realpath($file->getPath()), strlen(realpath($path))), + $this->nsSeparator, + '\\' + ); + + $classes[] = $this->prefixes[$path] . str_replace(DIRECTORY_SEPARATOR, '\\', $nsSuffix) . '\\' . str_replace($this->nsSeparator, '\\', $fileName); + } else { + $classes[] = str_replace($this->nsSeparator, '\\', $fileName); + } + } + } + } + + return $classes; + } + + /** + * {@inheritDoc} + */ + public function findMappingFile($className) + { + $defaultFileName = str_replace('\\', $this->nsSeparator, $className) . $this->fileExtension; + foreach ($this->paths as $path) { + if (! isset($this->prefixes[$path])) { + if (is_file($path . DIRECTORY_SEPARATOR . $defaultFileName)) { + return $path . DIRECTORY_SEPARATOR . $defaultFileName; + } + + continue; + } + + $prefix = $this->prefixes[$path]; + + if (strpos($className, $prefix . '\\') !== 0) { + continue; + } + + $filename = $path . '/' . strtr(substr($className, strlen($prefix) + 1), '\\', $this->nsSeparator) . $this->fileExtension; + if (is_file($filename)) { + return $filename; + } + } + + throw MappingException::mappingFileNotFound($className, substr($className, strrpos($className, '\\') + 1) . $this->fileExtension); + } +} + +class_exists(\Doctrine\Common\Persistence\Mapping\Driver\SymfonyFileLocator::class); diff --git a/vendor/doctrine/persistence/lib/Doctrine/Persistence/Mapping/MappingException.php b/vendor/doctrine/persistence/lib/Doctrine/Persistence/Mapping/MappingException.php new file mode 100644 index 000000000..9729e5f63 --- /dev/null +++ b/vendor/doctrine/persistence/lib/Doctrine/Persistence/Mapping/MappingException.php @@ -0,0 +1,98 @@ +supportsTypedPropertiesWorkaround = version_compare((string) phpversion(), '7.4.0') >= 0; + } + + /** + * {@inheritDoc} + */ + public function getParentClasses($class) + { + if (! class_exists($class)) { + throw MappingException::nonExistingClass($class); + } + + return class_parents($class); + } + + /** + * {@inheritDoc} + */ + public function getClassShortName($class) + { + $reflectionClass = new ReflectionClass($class); + + return $reflectionClass->getShortName(); + } + + /** + * {@inheritDoc} + */ + public function getClassNamespace($class) + { + $reflectionClass = new ReflectionClass($class); + + return $reflectionClass->getNamespaceName(); + } + + /** + * {@inheritDoc} + */ + public function getClass($class) + { + return new ReflectionClass($class); + } + + /** + * {@inheritDoc} + */ + public function getAccessibleProperty($class, $property) + { + $reflectionProperty = new ReflectionProperty($class, $property); + + if ($reflectionProperty->isPublic()) { + $reflectionProperty = new RuntimePublicReflectionProperty($class, $property); + } elseif ($this->supportsTypedPropertiesWorkaround && ! array_key_exists($property, $this->getClass($class)->getDefaultProperties())) { + $reflectionProperty = new TypedNoDefaultReflectionProperty($class, $property); + } + + $reflectionProperty->setAccessible(true); + + return $reflectionProperty; + } + + /** + * {@inheritDoc} + */ + public function hasPublicMethod($class, $method) + { + try { + $reflectionMethod = new ReflectionMethod($class, $method); + } catch (ReflectionException $e) { + return false; + } + + return $reflectionMethod->isPublic(); + } +} + +class_exists(\Doctrine\Common\Persistence\Mapping\RuntimeReflectionService::class); diff --git a/vendor/doctrine/persistence/lib/Doctrine/Persistence/Mapping/StaticReflectionService.php b/vendor/doctrine/persistence/lib/Doctrine/Persistence/Mapping/StaticReflectionService.php new file mode 100644 index 000000000..ac24e4572 --- /dev/null +++ b/vendor/doctrine/persistence/lib/Doctrine/Persistence/Mapping/StaticReflectionService.php @@ -0,0 +1,74 @@ +find($id). + * + * @param string $className The class name of the object to find. + * @param mixed $id The identity of the object to find. + * + * @return object|null The found object. + */ + public function find($className, $id); + + /** + * Tells the ObjectManager to make an instance managed and persistent. + * + * The object will be entered into the database as a result of the flush operation. + * + * NOTE: The persist operation always considers objects that are not yet known to + * this ObjectManager as NEW. Do not pass detached objects to the persist operation. + * + * @param object $object The instance to make managed and persistent. + * + * @return void + */ + public function persist($object); + + /** + * Removes an object instance. + * + * A removed object will be removed from the database as a result of the flush operation. + * + * @param object $object The object instance to remove. + * + * @return void + */ + public function remove($object); + + /** + * Merges the state of a detached object into the persistence context + * of this ObjectManager and returns the managed copy of the object. + * The object passed to merge will not become associated/managed with this ObjectManager. + * + * @deprecated Merge operation is deprecated and will be removed in Persistence 2.0. + * Merging should be part of the business domain of an application rather than + * a generic operation of ObjectManager. + * + * @param object $object + * + * @return object + */ + public function merge($object); + + /** + * Clears the ObjectManager. All objects that are currently managed + * by this ObjectManager become detached. + * + * @param string|null $objectName if given, only objects of this type will get detached. + * + * @return void + */ + public function clear($objectName = null); + + /** + * Detaches an object from the ObjectManager, causing a managed object to + * become detached. Unflushed changes made to the object if any + * (including removal of the object), will not be synchronized to the database. + * Objects which previously referenced the detached object will continue to + * reference it. + * + * @deprecated Detach operation is deprecated and will be removed in Persistence 2.0. Please use + * {@see ObjectManager::clear()} instead. + * + * @param object $object The object to detach. + * + * @return void + */ + public function detach($object); + + /** + * Refreshes the persistent state of an object from the database, + * overriding any local changes that have not yet been persisted. + * + * @param object $object The object to refresh. + * + * @return void + */ + public function refresh($object); + + /** + * Flushes all changes to objects that have been queued up to now to the database. + * This effectively synchronizes the in-memory state of managed objects with the + * database. + * + * @return void + */ + public function flush(); + + /** + * Gets the repository for a class. + * + * @param string $className + * + * @return ObjectRepository + */ + public function getRepository($className); + + /** + * Returns the ClassMetadata descriptor for a class. + * + * The class name must be the fully-qualified class name without a leading backslash + * (as it is returned by get_class($obj)). + * + * @param string $className + * + * @return ClassMetadata + */ + public function getClassMetadata($className); + + /** + * Gets the metadata factory used to gather the metadata of classes. + * + * @return ClassMetadataFactory + */ + public function getMetadataFactory(); + + /** + * Helper method to initialize a lazy loading proxy or persistent collection. + * + * This method is a no-op for other objects. + * + * @param object $obj + * + * @return void + */ + public function initializeObject($obj); + + /** + * Checks if the object is part of the current UnitOfWork and therefore managed. + * + * @param object $object + * + * @return bool + */ + public function contains($object); +} + +interface_exists(\Doctrine\Common\Persistence\ObjectManager::class); diff --git a/vendor/doctrine/persistence/lib/Doctrine/Persistence/ObjectManagerAware.php b/vendor/doctrine/persistence/lib/Doctrine/Persistence/ObjectManagerAware.php new file mode 100644 index 000000000..2593b7c66 --- /dev/null +++ b/vendor/doctrine/persistence/lib/Doctrine/Persistence/ObjectManagerAware.php @@ -0,0 +1,34 @@ +wrapped->find($className, $id); + } + + /** + * {@inheritdoc} + */ + public function persist($object) + { + $this->wrapped->persist($object); + } + + /** + * {@inheritdoc} + */ + public function remove($object) + { + $this->wrapped->remove($object); + } + + /** + * {@inheritdoc} + */ + public function merge($object) + { + return $this->wrapped->merge($object); + } + + /** + * {@inheritdoc} + */ + public function clear($objectName = null) + { + $this->wrapped->clear($objectName); + } + + /** + * {@inheritdoc} + */ + public function detach($object) + { + $this->wrapped->detach($object); + } + + /** + * {@inheritdoc} + */ + public function refresh($object) + { + $this->wrapped->refresh($object); + } + + /** + * {@inheritdoc} + */ + public function flush() + { + $this->wrapped->flush(); + } + + /** + * {@inheritdoc} + */ + public function getRepository($className) + { + return $this->wrapped->getRepository($className); + } + + /** + * {@inheritdoc} + */ + public function getClassMetadata($className) + { + return $this->wrapped->getClassMetadata($className); + } + + /** + * {@inheritdoc} + */ + public function getMetadataFactory() + { + return $this->wrapped->getMetadataFactory(); + } + + /** + * {@inheritdoc} + */ + public function initializeObject($obj) + { + $this->wrapped->initializeObject($obj); + } + + /** + * {@inheritdoc} + */ + public function contains($object) + { + return $this->wrapped->contains($object); + } +} + +class_exists(\Doctrine\Common\Persistence\ObjectManagerDecorator::class); diff --git a/vendor/doctrine/persistence/lib/Doctrine/Persistence/ObjectRepository.php b/vendor/doctrine/persistence/lib/Doctrine/Persistence/ObjectRepository.php new file mode 100644 index 000000000..7661be20d --- /dev/null +++ b/vendor/doctrine/persistence/lib/Doctrine/Persistence/ObjectRepository.php @@ -0,0 +1,64 @@ + + + + + + + + + diff --git a/web/modules/contrib/ckeditor_div_manager/LICENSE.txt b/web/modules/contrib/ckeditor_div_manager/LICENSE.txt new file mode 100644 index 000000000..d159169d1 --- /dev/null +++ b/web/modules/contrib/ckeditor_div_manager/LICENSE.txt @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/web/modules/contrib/ckeditor_div_manager/README.md b/web/modules/contrib/ckeditor_div_manager/README.md new file mode 100644 index 000000000..fb6789417 --- /dev/null +++ b/web/modules/contrib/ckeditor_div_manager/README.md @@ -0,0 +1,110 @@ +CKEditor Div Manager +==================== + +INTRODUCTION +------------ + +This module integrates the [Div Container Manager]( +https://ckeditor.com/cke4/addon/div) CKEditor plugin for Drupal 8. + +The plugin adds the ability to group content blocks under a div element as a +container, with styles and attributes optionally specified in a dialog. + + +REQUIREMENTS +------------ + +* CKEditor Module (Core) + + +INSTALLATION +------------ + +### Install via Composer (recommended) + +If you use Composer to manage dependencies, edit composer.json as follows. + +* Run `composer require --prefer-dist composer/installers` to ensure you have +the composer/installers package. This facilitates installation into directories +other than vendor using Composer. + +* In composer.json, make sure the "installer-paths" section in "extras" has an +entry for `type:drupal-library`. For example: + +```json +{ + "libraries/{$name}": ["type:drupal-library"] +} +``` + +* Add the following to the "repositories" section of composer.json: + +```json +{ + "type": "package", + "package": { + "name": "ckeditor/div", + "version": "4.10.1", + "type": "drupal-library", + "extra": { + "installer-name": "ckeditor/plugins/div" + }, + "dist": { + "url": "https://download.ckeditor.com/div/releases/div_4.10.1.zip", + "type": "zip" + } + } +} +``` + +* Run `composer require 'ckeditor/div:4.10.1'` to download the plugin. + +* Run `composer require 'drupal/ckeditor_div_manager:^1.0.0'` to download the +CKEditor Div Manager module, and enable it [as per usual]( +https://www.drupal.org/docs/8/extending-drupal-8/installing-drupal-8-modules). + + +### Install Manually + +* Download the [Div Container Manager](https://ckeditor.com/cke4/addon/div) +CKEditor plugin. + +* Extract and place the plugin contents in the following directory: +`/libraries/ckeditor/plugins/div/`. + +* Install the CKEditor Div Manager module [as per usual]( +https://www.drupal.org/docs/8/extending-drupal-8/installing-drupal-8-modules). + + +CONFIGURATION +------------- + +* Go to 'Text formats and editors' (`admin/config/content/formats`). + +* Click 'Configure' for any text format using CKEditor as the text editor. + +* Configure your CKEditor toolbar to include the Div Container Manager button. + +* If 'Limit allowed HTML tags and correct faulty HTML' is enabled, the Allowed +HTML tags need to be configured. At a minimum, the `
` tag needs to be +allowed. Some fields in the plugin dialog require additional attributes to be +allowed or they will be hidden. The full set is `
`. Inline +styles will not be allowed with this filter enabled. + +* Any classes or sets of classes defined in the editor's Styles dropdown for +the div element will carry over to the Style dropdown in the plugin dialog. + + +TROUBLESHOOTING +--------------- + +* This project only handles the bridge between Div Container Manager and Drupal. +For support of the plugin itself, please use their [project page]( +https://github.com/ckeditor/ckeditor-dev/tree/master/plugins/div). + + +MAINTAINERS +----------- +Current maintainers: + + * Corey Eiseman ([toegristle](https://www.drupal.org/u/toegristle)) diff --git a/web/modules/contrib/ckeditor_div_manager/ckeditor_div_manager.info.yml b/web/modules/contrib/ckeditor_div_manager/ckeditor_div_manager.info.yml new file mode 100644 index 000000000..80dd54ab3 --- /dev/null +++ b/web/modules/contrib/ckeditor_div_manager/ckeditor_div_manager.info.yml @@ -0,0 +1,13 @@ +name: CKEditor Div Manager +type: module +description: 'Adds the Div Container Manager plugin to CKEditor.' +core: 8.x +core_version_requirement: ^8 || ^9 +package: CKEditor +dependencies: + - drupal:ckeditor + +# Information added by Drupal.org packaging script on 2021-03-12 +version: '8.x-1.1' +project: 'ckeditor_div_manager' +datestamp: 1615574659 diff --git a/web/modules/contrib/ckeditor_div_manager/ckeditor_div_manager.install b/web/modules/contrib/ckeditor_div_manager/ckeditor_div_manager.install new file mode 100644 index 000000000..6066ab4e1 --- /dev/null +++ b/web/modules/contrib/ckeditor_div_manager/ckeditor_div_manager.install @@ -0,0 +1,41 @@ + t('CKEditor Div Manager'), + 'value' => t('Plugin detected'), + 'severity' => REQUIREMENT_OK, + ]; + } + else { + $requirements['div'] = [ + 'title' => t('CKEditor Div Manager'), + 'value' => t('Plugin not detected'), + 'severity' => REQUIREMENT_ERROR, + 'description' => t('The CKEditor plugin Div Container Manager is + required. Download here and copy to + :path.', [ + ':url' => 'https://ckeditor.com/cke4/addon/div', + ':path' => $path, + ]), + ]; + } + } + + return $requirements; +} diff --git a/web/modules/contrib/ckeditor_div_manager/composer.json b/web/modules/contrib/ckeditor_div_manager/composer.json new file mode 100644 index 000000000..24658c424 --- /dev/null +++ b/web/modules/contrib/ckeditor_div_manager/composer.json @@ -0,0 +1,18 @@ +{ + "name": "drupal/ckeditor_div_manager", + "description": "Adds the Div Container Manager plugin to CKEditor.", + "type": "drupal-module", + "homepage": "https://www.drupal.org/project/ckeditor_div_manager", + "license": "GPL-2.0+", + "authors": [ + { + "name": "Corey Eiseman", + "homepage": "https://www.drupal.org/u/toegristle", + "role": "Maintainer" + } + ], + "support": { + "issues": "https://www.drupal.org/project/issues/ckeditor_div_manager", + "source": "https://cgit.drupalcode.org/ckeditor_div_manager" + } +} diff --git a/web/modules/contrib/ckeditor_div_manager/src/Plugin/CKEditorPlugin/CKEditorDiv.php b/web/modules/contrib/ckeditor_div_manager/src/Plugin/CKEditorPlugin/CKEditorDiv.php new file mode 100644 index 000000000..144becae3 --- /dev/null +++ b/web/modules/contrib/ckeditor_div_manager/src/Plugin/CKEditorPlugin/CKEditorDiv.php @@ -0,0 +1,44 @@ + [ + 'label' => t('Div Container Manager'), + 'image' => 'libraries/ckeditor/plugins/div/icons/creatediv.png', + ], + ]; + } + + /** + * {@inheritdoc} + */ + public function getConfig(Editor $editor) { + return []; + } + +} diff --git a/web/modules/contrib/devel/.docker/zz-php.ini b/web/modules/contrib/devel/.docker/zz-php.ini new file mode 100644 index 000000000..582337f23 --- /dev/null +++ b/web/modules/contrib/devel/.docker/zz-php.ini @@ -0,0 +1,5 @@ +[PHP] +variables_order = GPCS +error_reporting = E_ALL +date.timezone = "UTC" +sendmail_path = "true" diff --git a/web/modules/contrib/devel/.gitignore b/web/modules/contrib/devel/.gitignore new file mode 100644 index 000000000..4a5c9bed1 --- /dev/null +++ b/web/modules/contrib/devel/.gitignore @@ -0,0 +1,33 @@ +/vendor/ +vendor +/web/ +web +/node_modules/ +.env +composer.lock +yarn.lock +/.editorconfig +/.gitattributes + +#PHPUnit output +junit.xml +.phpunit.result.cache + +# Ignore local overrides. +docker-compose.override.yml +/.csslintrc +/.eslintignore +/.eslintrc.json +/.ht.router.php +/.htaccess +/INSTALL.txt +/README.txt +/autoload.php +/example.gitignore +/index.php +/robots.txt +/update.php +/web.config +/composer.spoons.json +/composer.spoons.lock +/.env diff --git a/web/modules/contrib/devel/.gitlab-ci.yml b/web/modules/contrib/devel/.gitlab-ci.yml new file mode 100644 index 000000000..ea269d550 --- /dev/null +++ b/web/modules/contrib/devel/.gitlab-ci.yml @@ -0,0 +1,29 @@ +include: + - project: 'drupalspoons/composer-plugin' + # Best practice is to pin to a tag or a SHA1. https://docs.gitlab.com/ee/ci/yaml/#includefile + ref: "2.1.0" + # The template below may be inspected at https://gitlab.com/drupalspoons/composer-plugin/-/blob/master/templates/.gitlab-ci.yml + file: 'templates/.gitlab-ci.yml' + +# +# Projects may override anything in the template above. +# The code below is specific to devel project. Comment it out or delete it when +# copying this file to your new project. +# + +# Run tests on Drupal 9.1 by default, including a phpspec/prophecy-phpunit requirement. +composer_node: + variables: + # https://getcomposer.org/doc/articles/versions.md#next-significant-release-operators + DRUPAL_CORE_CONSTRAINT: ~9.1.0 + after_script: + # See https://www.drupal.org/project/drupal/issues/3182653 + # This will fail on PHPUnit 8-, that is OK as its not needed there. + - vendor/bin/spoon require --no-progress phpspec/prophecy-phpunit:^2 || true + + +# Add /webprofiler to ignored paths. +phpcs: + script: + - PWD=$(pwd) && vendor/bin/phpcs --runtime-set ignore_warnings_on_exit 1 --runtime-set ignore_errors_on_exit 1 --ignore=$PWD/web/,$PWD/vendor/,$PWD/webprofiler/ --report-junit=junit.xml --report-full --report-summary + diff --git a/web/modules/contrib/devel/.travis.yml b/web/modules/contrib/devel/.travis.yml new file mode 100644 index 000000000..b1d1ab886 --- /dev/null +++ b/web/modules/contrib/devel/.travis.yml @@ -0,0 +1,109 @@ +language: php + +# The Travis CI container mode has random functional test fails, so we must use +# sudo here. +sudo: true + +php: + - 7.1 + - 7.3 + +services: + - mysql + +env: + global: + - MODULE=devel + matrix: + - DRUPAL_CORE=8.8.x + - DRUPAL_CORE=8.9.x + - DRUPAL_CORE=9.0.x + +matrix: + fast_finish: true + # PHP7.1 is not supported from Core 9.0 onwards. + exclude: + - php: 7.1 + env: DRUPAL_CORE=9.0.x + +# Be sure to cache composer downloads. +cache: + directories: + - $HOME/.composer + +before_script: + - echo $MODULE + + # Remove Xdebug as we don't need it and it causes + # PHP Fatal error: Maximum function nesting level of '256' reached. + # We also don't care if that file exists or not on PHP 7. + - phpenv config-rm xdebug.ini || true + + # Navigate out of module directory to prevent blown stack by recursive module + # lookup. + - cd .. + + # Create database. + - mysql -e "create database $MODULE" + # Export database variable for kernel tests. + - export SIMPLETEST_DB=mysql://root:@127.0.0.1/$MODULE + + # Download Drupal core from the Github mirror because it is faster. + - travis_retry git clone --branch $DRUPAL_CORE --depth 1 https://github.com/drupal/drupal.git + - cd drupal + # Store the path to Drupal root. + - DRUPAL_ROOT=$(pwd) + - echo $DRUPAL_ROOT + + # Make a directory for our module and copy the built source into it. + - mkdir $DRUPAL_ROOT/modules/$MODULE + - cp -R $TRAVIS_BUILD_DIR/* $DRUPAL_ROOT/modules/$MODULE/ + + # Apply patch to reverse the addition of doctrine/debug in composer.json so that tests can run again. + - cd $DRUPAL_ROOT/modules/$MODULE; + - wget -q -O - https://www.drupal.org/files/issues/2020-04-17/3125678-9.remove-doctrine-common.patch | patch -p1 --verbose; + - cd $DRUPAL_ROOT; + + # Run composer self-update and install. + - travis_retry composer self-update && travis_retry composer install + + # Run composer update in the module directory in order to fetch dependencies. + - travis_retry composer update -d $DRUPAL_ROOT/modules/$MODULE + + # Install drush + - travis_retry composer require drush/drush:"^9.0 || ^10.0" + + # Coder is already installed as part of composer install. We just need to set + # the installed_paths to pick up the Drupal standards. + - $DRUPAL_ROOT/vendor/bin/phpcs --config-set installed_paths $DRUPAL_ROOT/vendor/drupal/coder/coder_sniffer + + # Start a web server on port 8888, run in the background. + - php -S localhost:8888 & + + # Export web server URL for browser tests. + - export SIMPLETEST_BASE_URL=http://localhost:8888 + + # Interim patch for 9.0 only to avoid the Webprofiler toolbar halting the test run with 'class not found'. + - if [ $DRUPAL_CORE == "9.0.x" ]; then + cd $DRUPAL_ROOT/modules/$MODULE; + wget -q -O - https://www.drupal.org/files/issues/2020-04-07/3097125-33.replace-JavascriptTestBase.patch | patch -p1 --verbose; + fi + +script: + # Run the PHPUnit tests. + - cd $DRUPAL_ROOT + - ./vendor/bin/phpunit -c ./core/phpunit.xml.dist --verbose --group=devel,devel_generate,webprofiler ./modules/$MODULE + + # Check for coding standards. First change directory to our module. + - cd $DRUPAL_ROOT/modules/$MODULE + + # List all the sniffs that were used. + - $DRUPAL_ROOT/vendor/bin/phpcs --version + - $DRUPAL_ROOT/vendor/bin/phpcs -i + - $DRUPAL_ROOT/vendor/bin/phpcs -e + + # Show the violations in detail and do not fail for any errors or warnings. + - $DRUPAL_ROOT/vendor/bin/phpcs -s --report-width=130 --colors --runtime-set ignore_warnings_on_exit 1 --runtime-set ignore_errors_on_exit 1 . + + # Run again to give a summary and total count. + - $DRUPAL_ROOT/vendor/bin/phpcs --report-width=130 --colors --runtime-set ignore_warnings_on_exit 1 --runtime-set ignore_errors_on_exit 1 --report=summary . diff --git a/web/modules/contrib/devel/CODEOWNERS b/web/modules/contrib/devel/CODEOWNERS new file mode 100644 index 000000000..006360437 --- /dev/null +++ b/web/modules/contrib/devel/CODEOWNERS @@ -0,0 +1,3 @@ +/webprofiler/ @lussoluca +/src/Commands/ @weitzman + diff --git a/web/modules/contrib/devel/LICENSE.txt b/web/modules/contrib/devel/LICENSE.txt new file mode 100644 index 000000000..d159169d1 --- /dev/null +++ b/web/modules/contrib/devel/LICENSE.txt @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/web/modules/contrib/devel/README.md b/web/modules/contrib/devel/README.md new file mode 100644 index 000000000..47ab28cf7 --- /dev/null +++ b/web/modules/contrib/devel/README.md @@ -0,0 +1,55 @@ +[[_TOC_]] + +#### Introduction + +Devel module contains helper functions and pages for Drupal developers and +inquisitive admins: + + - A block and toolbar for quickly accessing devel pages + - Debug functions for inspecting a variable such as `dpm($variable)` + - Debug a SQL query `dpq($query` or print a backtrace `ddebug_backtrace()` + - A block for masquerading as other users (useful for testing) + - A mail-system class which redirects outbound email to files + - Drush commands such as `fn-hook`, `fn-event`, `token`, `uuid`, and `devel-services` + - *Webprofiler*. Adds a debug bar at bottom of all pages with tons of useful + information like a query list, cache hit/miss data, memory profiling, page + speed, php info, session info, etc. + - *Devel Generate*. Bulk creates nodes, users, comment, taxonomy, media, menus for development. Has + Drush integration. + +This module is safe to use on a production site. Just be sure to only grant +_access development information_ permission to developers. + +#### Collaboration +- https://gitlab.com/drupalspoons/devel is our workplace for code, MRs, and CI. See +[DrupalSpoons](https://gitlab.com/drupalcontrib/webmasters/-/blob/master/README.md) +for more info. +- We push back to git.drupalcode.org in order to keep +[Security Team](https://www.drupal.org/security) coverage and packages.drupal.org integration. +- Chat with us at [#devel](https://drupal.slack.com/archives/C012WAW1MH6) on Drupal Slack. + +#### Local Development +1. Clone devel `git clone https://gitlab.com/drupalforks/devel.git` +1. `cd devel` +1. Install the composer plugin from https://gitlab.com/drupalspoons/composer-plugin. Your source tree now looks like: +![Folder tree](/icons/folder.png) +1. Configure a web server to serve devel's `/web` directory as docroot. __Either__ of these works fine: + 1. `vendor/bin/spoon runserver` + 1. Setup Apache/Nginx/Other. A virtual host will work fine. Any domain name works. +1. Configure a database server and a database. +1. Install a testing site `vendor/bin/spoon si -- --db-url=mysql://user:pass@localhost/db`. Adjust as needed. + +#### Testing +- [CI docs](https://gitlab.com/drupalspoons/webmasters/-/blob/master/docs/ci.md) gives info on running tests. +- See [develCommandsTest.php](tests/src/Functional/DevelCommandsTest.php) for an example of Drush command testing. This uses [Drush Test Traits](https://www.drush.org/contribute/#drush-test-traits). + +#### Version Compatibility +| Devel version | Drupal core | PHP | Drush | +| ------ | ------ | ----- | ----- | +| 4.x |8.8+ | 7.2+ | 9+ +| 8.x-2.x | 8.x |7.0+ | 8+ + + +#### Maintainers + +See https://gitlab.com/groups/drupaladmins/devel/-/group_members. diff --git a/web/modules/contrib/devel/composer.json b/web/modules/contrib/devel/composer.json new file mode 100644 index 000000000..3b60a26db --- /dev/null +++ b/web/modules/contrib/devel/composer.json @@ -0,0 +1,33 @@ +{ + "name": "drupal/devel", + "description": "Various blocks, pages, and functions for developers.", + "type": "drupal-module", + "support": { + "issues": "https://gitlab.com/drupalspoons/devel/-/issues", + "slack": "https://drupal.slack.com/archives/C012WAW1MH6", + "source": "https://gitlab.com/drupalspoons/devel" + }, + "license": "GPL-2.0-or-later", + "minimum-stability": "dev", + "prefer-stable": true, + "require": { + "symfony/var-dumper": "^4 || ^5", + "doctrine/common": "^2.7" + }, + "require-dev": { + "drush/drush": "^10" + }, + "suggest": { + "kint-php/kint": "Kint provides an informative display of arrays/objects. Useful for debugging and developing." + }, + "conflict": { + "kint-php/kint": "<3" + }, + "extra": { + "drush": { + "services": { + "drush.services.yml": "^9 || ^10" + } + } + } +} diff --git a/web/modules/contrib/devel/config/install/devel.settings.yml b/web/modules/contrib/devel/config/install/devel.settings.yml new file mode 100644 index 000000000..bd0c269c1 --- /dev/null +++ b/web/modules/contrib/devel/config/install/devel.settings.yml @@ -0,0 +1,10 @@ +page_alter: FALSE +raw_names: FALSE +error_handlers: + 1: 1 +rebuild_theme: FALSE +debug_mail_file_format: '%to-%subject-%datetime.mail.txt' +debug_mail_directory: 'temporary://devel-mails' +devel_dumper: 'default' +debug_logfile: 'temporary://drupal_debug.txt' +debug_pre: TRUE diff --git a/web/modules/contrib/devel/config/install/devel.toolbar.settings.yml b/web/modules/contrib/devel/config/install/devel.toolbar.settings.yml new file mode 100644 index 000000000..b12c5bdc9 --- /dev/null +++ b/web/modules/contrib/devel/config/install/devel.toolbar.settings.yml @@ -0,0 +1,8 @@ +toolbar_items: + - 'devel.admin_settings_link' + - 'devel.cache_clear' + - 'devel.container_info.service' + - 'devel.menu_rebuild' + - 'devel.reinstall' + - 'devel.route_info' + - 'devel.run_cron' diff --git a/web/modules/contrib/devel/config/install/system.menu.devel.yml b/web/modules/contrib/devel/config/install/system.menu.devel.yml new file mode 100644 index 000000000..36de4c40a --- /dev/null +++ b/web/modules/contrib/devel/config/install/system.menu.devel.yml @@ -0,0 +1,10 @@ +langcode: en +status: true +dependencies: + enforced: + module: + - devel +id: devel +label: Development +description: 'Links related to Devel module.' +locked: true diff --git a/web/modules/contrib/devel/config/schema/devel.schema.yml b/web/modules/contrib/devel/config/schema/devel.schema.yml new file mode 100644 index 000000000..dd6ae1cb2 --- /dev/null +++ b/web/modules/contrib/devel/config/schema/devel.schema.yml @@ -0,0 +1,59 @@ +# Schema for the configuration files of the Devel module. + +devel.settings: + type: config_object + label: 'Devel settings' + mapping: + page_alter: + type: boolean + label: 'Page alter' + raw_names: + type: boolean + label: 'Raw names' + error_handlers: + label: 'Error handlers' + type: sequence + sequence: + type: integer + rebuild_theme: + type: boolean + label: 'Rebuild theme information' + debug_mail_file_format: + type: string + label: 'Mail debug file format' + debug_mail_directory: + type: string + label: 'Mail debug directory' + devel_dumper: + type: string + label: 'Devel variable dumper' + debug_logfile: + type: string + label: 'Devel debug log file' + debug_pre: + type: boolean + label: 'Wrap debug output in pre tags' + +devel.toolbar.settings: + type: config_object + label: 'Devel Toolbar settings' + mapping: + toolbar_items: + type: sequence + label: 'Toolbar items' + sequence: + type: string + +block.settings.devel_switch_user: + type: block_settings + label: 'Switch user block' + mapping: + list_size: + type: integer + label: 'List size' + include_anon: + type: boolean + label: 'Include Anonymous user' + show_form: + type: boolean + label: 'Show search form' diff --git a/web/modules/contrib/devel/css/devel.css b/web/modules/contrib/devel/css/devel.css new file mode 100644 index 000000000..f50eb15b5 --- /dev/null +++ b/web/modules/contrib/devel/css/devel.css @@ -0,0 +1,28 @@ +/** + * Dumpers + */ +.devel-dumper .details-wrapper { + max-height: 450px; + margin-right: 3px; + overflow: auto; +} + +/** + * Switch User block + */ +.region-content .block-devel-switch-user ul, +.site-footer .block-devel-switch-user ul { + display: flex; + flex-flow: row wrap; + padding: 0; +} + +.region-content .block-devel-switch-user ul li, +.site-footer .block-devel-switch-user ul li { + display: block; + padding-right: 2em; +} + +.devel-switchuser-form { + margin-top: 0.5em; +} diff --git a/web/modules/contrib/devel/css/devel.toolbar.css b/web/modules/contrib/devel/css/devel.toolbar.css new file mode 100644 index 000000000..8a8b791bc --- /dev/null +++ b/web/modules/contrib/devel/css/devel.toolbar.css @@ -0,0 +1,42 @@ +/** + * @file + * Styling for devel toolbar module. + */ + +.toolbar .toolbar-tray-vertical .edit-devel-toolbar { + text-align: right; /* LTR */ + padding: 1em; +} + +[dir="rtl"] .toolbar .toolbar-tray-vertical .edit-devel-toolbar { + text-align: left; +} + +.toolbar .toolbar-tray-horizontal .edit-devel-toolbar { + float: right; /* LTR */ +} + +[dir="rtl"] .toolbar .toolbar-tray-horizontal .edit-devel-toolbar { + float: left; +} + +.toolbar .toolbar-tray-horizontal .toolbar-menu { + float: left; /* LTR */ +} + +[dir="rtl"] .toolbar .toolbar-tray-horizontal .toolbar-menu { + float: right; +} + +.toolbar .toolbar-bar .toolbar-icon-devel:before { + background-image: url(../icons/bebebe/cog.svg); +} + +.toolbar-bar .toolbar-icon-devel:active:before, +.toolbar-bar .toolbar-icon-devel.is-active:before { + background-image: url(../icons/ffffff/cog.svg); +} + +.toolbar-horizontal .toolbar-horizontal-item-hidden { + display: none; +} diff --git a/web/modules/contrib/devel/devel.api.php b/web/modules/contrib/devel/devel.api.php new file mode 100644 index 000000000..e8c4b49ba --- /dev/null +++ b/web/modules/contrib/devel/devel.api.php @@ -0,0 +1,25 @@ + t('Devel module enabled'), + 'description' => t("The Devel module provides access to internal debugging information; therefore it's recommended to disable this module on sites in production."), + 'severity' => REQUIREMENT_INFO, + ]; + } + + return $requirements; +} + +/** + * Set the default devel dumper plugin. + */ +function devel_update_8001() { + $kint_enabled = \Drupal::moduleHandler()->moduleExists('kint'); + + $default_dumper = $kint_enabled ? 'kint' : 'default'; + + // Set the default dumper plugin to kint if kint module is available. + \Drupal::configFactory()->getEditable('devel.settings') + ->set('devel_dumper', $default_dumper) + ->save(TRUE); +} + +/** + * Add enforced dependencies to system.menu.devel. + */ +function devel_update_8002() { + $config = \Drupal::configFactory()->getEditable('system.menu.devel'); + $dependencies = $config->get('dependencies'); + $dependencies['enforced']['module'][] = 'devel'; + $config->set('dependencies', $dependencies)->save(TRUE); +} diff --git a/web/modules/contrib/devel/devel.libraries.yml b/web/modules/contrib/devel/devel.libraries.yml new file mode 100644 index 000000000..ee32ae996 --- /dev/null +++ b/web/modules/contrib/devel/devel.libraries.yml @@ -0,0 +1,17 @@ +devel: + version: 0 + css: + theme: + css/devel.css: {} + +devel-toolbar: + version: VERSION + css: + component: + css/devel.toolbar.css: {} + +devel-table-filter: + version: VERSION + js: {} + dependencies: + - system/drupal.system.modules diff --git a/web/modules/contrib/devel/devel.links.menu.yml b/web/modules/contrib/devel/devel.links.menu.yml new file mode 100644 index 000000000..4becee6f1 --- /dev/null +++ b/web/modules/contrib/devel/devel.links.menu.yml @@ -0,0 +1,89 @@ +devel.admin_settings: + title: 'Devel settings' + description: 'Helper functions, pages, and blocks to assist Drupal developers. The devel blocks can be managed via the block administration page.' + route_name: devel.admin_settings + parent: 'system.admin_config_development' +devel.admin_settings_link: + title: 'Devel settings' + description: 'Helper functions, pages, and blocks to assist Drupal developers. The devel blocks can be managed via the block administration page.' + route_name: devel.admin_settings + menu_name: devel +devel.configs_list: + title: 'Config editor' + description: 'Edit configuration.' + route_name: devel.configs_list + menu_name: devel +devel.reinstall: + title: 'Reinstall Modules' + route_name: devel.reinstall + menu_name: devel + class: \Drupal\devel\Plugin\Menu\DestinationMenuLink +devel.menu_rebuild: + title: 'Rebuild Menu' + route_name: devel.menu_rebuild + menu_name: devel + class: \Drupal\devel\Plugin\Menu\DestinationMenuLink +devel.state_system_page: + title: 'State editor' + description: 'Edit state system values.' + route_name: devel.state_system_page + menu_name: devel +devel.theme_registry: + title: 'Theme registry' + route_name: devel.theme_registry + menu_name: devel +devel.entity_info_page: + title: 'Entity Info' + route_name: devel.entity_info_page +# parent: 'system.admin_config_development' + menu_name: devel +devel.field_info_page: + title: 'Field Info' + route_name: devel.field_info_page +# parent: 'system.admin_config_development' + menu_name: devel +devel.phpinfo: + title: 'PHPinfo()' + route_name: system.php + menu_name: devel +devel.session: + title: 'View Session' + route_name: devel.session + menu_name: devel +devel.elements_page: + title: 'Element Info' + route_name: devel.elements_page + menu_name: devel +devel.cache_clear: + title: 'Cache clear' + route_name: devel.cache_clear + menu_name: devel + class: \Drupal\devel\Plugin\Menu\DestinationMenuLink +devel.run_cron: + title: 'Run cron' + route_name: devel.run_cron + menu_name: devel + class: \Drupal\devel\Plugin\Menu\DestinationMenuLink + +# Container info +devel.container_info.service: + title: 'Container Info' + route_name: devel.container_info.service + menu_name: devel + +# Routes info +devel.route_info: + title: 'Routes Info' + route_name: devel.route_info + menu_name: devel +devel.route_info.item: + title: 'Current route info' + route_name: devel.route_info.item + menu_name: devel + class: \Drupal\devel\Plugin\Menu\RouteDetailMenuLink + +# Event info +devel.event_info: + title: 'Events Info' + route_name: devel.event_info + menu_name: devel diff --git a/web/modules/contrib/devel/devel.links.task.yml b/web/modules/contrib/devel/devel.links.task.yml new file mode 100644 index 000000000..9c7d349e0 --- /dev/null +++ b/web/modules/contrib/devel/devel.links.task.yml @@ -0,0 +1,18 @@ +devel.entities: + class: \Drupal\Core\Menu\LocalTaskDefault + deriver: \Drupal\devel\Plugin\Derivative\DevelLocalTask +devel.admin_settings: + title: 'Settings' + route_name: devel.admin_settings + base_route: devel.admin_settings + weight: 0 + +# Container info +devel.container_info.service: + title: 'Services' + route_name: devel.container_info.service + base_route: devel.container_info.service +devel.container_info.parameter: + title: 'Parameters' + route_name: devel.container_info.parameter + base_route: devel.container_info.service diff --git a/web/modules/contrib/devel/devel.module b/web/modules/contrib/devel/devel.module new file mode 100644 index 000000000..21c23fbb5 --- /dev/null +++ b/web/modules/contrib/devel/devel.module @@ -0,0 +1,758 @@ +' . t('About') . ''; + $output .= '

' . t('The Devel module provides a suite of modules containing fun for module developers and themers. For more information, see the online documentation for the Devel module.', [':url' => 'https://www.drupal.org/docs/8/modules/devel']) . '

'; + $output .= '

' . t('Uses') . '

'; + $output .= '
'; + $output .= '
' . t('Inspecting Service Container') . '
'; + $output .= '
' . t('The module allows you to inspect Services and Parameters registered in the Service Container. You can see those informations on Container info page.', [':url' => Url::fromRoute('devel.container_info.service')->toString()]) . '
'; + $output .= '
' . t('Inspecting Routes') . '
'; + $output .= '
' . t('The module allows you to inspect routes information, gathering all routing data from .routing.yml files and from classes which subscribe to the route build/alter events. You can see those informations on Routes info page.', [':url' => Url::fromRoute('devel.route_info')->toString()]) . '
'; + $output .= '
' . t('Inspecting Events') . '
'; + $output .= '
' . t('The module allow you to inspect listeners registered in the event dispatcher. You can see those informations on Events info page.', [':url' => Url::fromRoute('devel.event_info')->toString()]) . '
'; + $output .= '
'; + return $output; + + case 'devel.container_info.service': + case 'devel.container_info.parameter': + $output = ''; + $output .= '

' . t('Displays Services and Parameters registered in the Service Container. For more informations on the Service Container, see the Symfony online documentation.', [':url' => 'http://symfony.com/doc/current/service_container.html']) . '

'; + return $output; + + case 'devel.route_info': + $output = ''; + $output .= '

' . t('Displays registered routes for the site. For a complete overview of the routing system, see the online documentation.', [':url' => 'https://www.drupal.org/docs/8/api/routing-system']) . '

'; + return $output; + + case 'devel.event_info': + $output = ''; + $output .= '

' . t('Displays events and listeners registered in the event dispatcher. For a complete overview of the event system, see the Symfony online documentation.', [':url' => 'http://symfony.com/doc/current/components/event_dispatcher.html']) . '

'; + return $output; + + case 'devel.reinstall': + $output = '

' . t('Warning - will delete your module tables and configuration.') . '

'; + $output .= '

' . t('Uninstall and then install the selected modules. hook_uninstall() and hook_install() will be executed and the schema version number will be set to the most recent update number.') . '

'; + return $output; + + case 'devel/session': + return '

' . t('Here are the contents of your $_SESSION variable.') . '

'; + + case 'devel.state_system_page': + return '

' . t('This is a list of state variables and their values. For more information read online documentation of State API in Drupal 8.', [':documentation' => "https://www.drupal.org/developing/api/8/state"]) . '

'; + + case 'devel.layout_info': + $output = ''; + $output .= '

' . t('Displays layouts available to the site. For a complete overview of the layout system, see the Layout API documentation.', [':url' => 'https://www.drupal.org/docs/8/api/layout-api']) . '

'; + return $output; + + } +} + +/** + * Implements hook_entity_type_alter(). + */ +function devel_entity_type_alter(array &$entity_types) { + return \Drupal::service('class_resolver') + ->getInstanceFromDefinition(EntityTypeInfo::class) + ->entityTypeAlter($entity_types); +} + +/** + * Implements hook_entity_operation(). + */ +function devel_entity_operation(EntityInterface $entity) { + return \Drupal::service('class_resolver') + ->getInstanceFromDefinition(EntityTypeInfo::class) + ->entityOperation($entity); +} + +/** + * Implements hook_toolbar(). + */ +function devel_toolbar() { + return \Drupal::service('class_resolver') + ->getInstanceFromDefinition(ToolbarHandler::class) + ->toolbar(); +} + +/** + * Implements hook_menu_links_discovered_alter(). + */ +function devel_menu_links_discovered_alter(&$links) { + // Conditionally add the Layouts info menu link. + if (\Drupal::moduleHandler()->moduleExists('layout_discovery')) { + $links['devel.layout_info'] = [ + 'title' => new TranslatableMarkup('Layouts Info'), + 'route_name' => 'devel.layout_info', + 'description' => new TranslatableMarkup('Overview of layouts available to the site.'), + 'menu_name' => 'devel', + ]; + } +} + +/** + * Implements hook_local_tasks_alter(). + */ +function devel_local_tasks_alter(&$local_tasks) { + if (\Drupal::moduleHandler()->moduleExists('toolbar')) { + $local_tasks['devel.toolbar.settings_form'] = [ + 'title' => 'Toolbar Settings', + 'base_route' => 'devel.admin_settings', + 'route_name' => 'devel.toolbar.settings_form', + 'class' => LocalTaskDefault::class, + 'options' => [], + ]; + } +} + +/** + * Sets message. + */ +function devel_set_message($msg, $type = NULL) { + if (function_exists('drush_log')) { + drush_log($msg, $type); + } + else { + \Drupal::messenger()->addMessage($msg, $type, TRUE); + } +} + +/** + * Gets error handlers. + */ +function devel_get_handlers() { + $error_handlers = \Drupal::config('devel.settings')->get('error_handlers'); + if (!empty($error_handlers)) { + unset($error_handlers[DEVEL_ERROR_HANDLER_NONE]); + } + return $error_handlers; +} + +/** + * Sets a new error handler or restores the prior one. + */ +function devel_set_handler($handlers) { + if (empty($handlers)) { + restore_error_handler(); + } + elseif (count($handlers) == 1 && isset($handlers[DEVEL_ERROR_HANDLER_STANDARD])) { + // Do nothing. + } + else { + set_error_handler('backtrace_error_handler'); + } +} + +/** + * Displays backtrace showing the route of calls to the current error. + * + * @param int $error_level + * The level of the error raised. + * @param string $message + * The error message. + * @param string $filename + * (optional) The filename that the error was raised in. + * @param int $line + * (optional) The line number the error was raised at. + * @param array $context + * (optional) An array that points to the active symbol table at the point the error + * occurred. + */ +function backtrace_error_handler($error_level, $message, $filename = NULL, $line = NULL, $context = NULL) { + // Hide stack trace and parameters from unqualified users. + if (!\Drupal::currentUser()->hasPermission('access devel information')) { + // Do what core does in bootstrap.inc and errors.inc. + // (We need to duplicate the core code here rather than calling it + // to avoid having the backtrace_error_handler() on top of the call stack.) + if ($error_level & error_reporting()) { + $types = drupal_error_levels(); + list($severity_msg, $severity_level) = $types[$error_level]; + $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); + $caller = Error::getLastCaller($backtrace); + + // We treat recoverable errors as fatal. + _drupal_log_error([ + '%type' => isset($types[$error_level]) ? $severity_msg : 'Unknown error', + '@message' => $message, + '%function' => $caller['function'], + '%file' => $caller['file'], + '%line' => $caller['line'], + 'severity_level' => $severity_level, + 'backtrace' => $backtrace, + ], $error_level == E_RECOVERABLE_ERROR); + } + + return; + } + + // Don't respond to the error if it was suppressed with a '@'. + if (error_reporting() == 0) { + return; + } + + // Don't respond to warning caused by ourselves. + if (preg_match('#Cannot modify header information - headers already sent by \\([^\\)]*[/\\\\]devel[/\\\\]#', $message)) { + return; + } + + if ($error_level & error_reporting()) { + // Only write each distinct NOTICE message once, as repeats do not give any + // further information and can choke the page output. + if ($error_level == E_NOTICE) { + static $written = []; + if (!empty($written[$line][$filename][$message])) { + return; + } + $written[$line][$filename][$message] = TRUE; + } + + $types = drupal_error_levels(); + list($severity_msg, $severity_level) = $types[$error_level]; + + $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); + $caller = Error::getLastCaller($backtrace); + $variables = [ + '%type' => isset($types[$error_level]) ? $severity_msg : 'Unknown error', + '@message' => $message, + '%function' => $caller['function'], + '%file' => $caller['file'], + '%line' => $caller['line'], + ]; + $msg = t('%type: @message in %function (line %line of %file).', $variables); + + // Show message if error_level is ERROR_REPORTING_DISPLAY_SOME or higher. + // (This is Drupal's error_level, which is different from $error_level, + // and we purposely ignore the difference between _SOME and _ALL, + // see #970688!) + if (\Drupal::config('system.logging')->get('error_level') != 'hide') { + $error_handlers = devel_get_handlers(); + if (!empty($error_handlers[DEVEL_ERROR_HANDLER_STANDARD])) { + \Drupal::messenger()->addMessage($msg, ($severity_level <= RfcLogLevel::NOTICE ? MessengerInterface::TYPE_ERROR : MessengerInterface::TYPE_WARNING), TRUE); + } + if (!empty($error_handlers[DEVEL_ERROR_HANDLER_BACKTRACE_KINT])) { + print kpr(ddebug_backtrace(TRUE, 1), TRUE, $msg); + } + if (!empty($error_handlers[DEVEL_ERROR_HANDLER_BACKTRACE_DPM])) { + dpm(ddebug_backtrace(TRUE, 1), $msg, 'warning'); + } + } + + \Drupal::logger('php')->log($severity_level, $msg); + } +} + +/** + * Implements hook_page_attachments_alter(). + */ +function devel_page_attachments_alter(&$page) { + if (\Drupal::currentUser()->hasPermission('access devel information') && \Drupal::config('devel.settings')->get('page_alter')) { + dpm($page, 'page'); + } +} + +/** + * Dumps information about a variable. + * + * Wrapper for DevelDumperManager::dump(). + * + * @param mixed $input + * The variable to dump. + * @param string $name + * (optional) The label to output before variable, defaults to NULL. + * @param string $plugin_id + * (optional) The plugin ID, defaults to NULL. + * + * @see \Drupal\devel\DevelDumperManager::dump() + */ +function devel_dump($input, $name = NULL, $plugin_id = NULL) { + \Drupal::service('devel.dumper')->dump($input, $name, $plugin_id); +} + +/** + * Returns a string representation of a variable. + * + * Wrapper for DevelDumperManager::export(). + * + * @param mixed $input + * The variable to dump. + * @param string $name + * (optional) The label to output before variable, defaults to NULL. + * @param string $plugin_id + * (optional) The plugin ID, defaults to NULL. + * + * @return string + * String representation of a variable. + * + * @see \Drupal\devel\DevelDumperManager::export() + */ +function devel_export($input, $name = NULL, $plugin_id = NULL) { + return \Drupal::service('devel.dumper')->export($input, $name, $plugin_id); +} + +/** + * Sets a message with a string representation of a variable. + * + * Wrapper for DevelDumperManager::message(). + * + * @param mixed $input + * The variable to dump. + * @param string $name + * (optional) The label to output before variable, defaults to NULL. + * @param string $type + * (optional) The message's type. Defaults to 'status'. + * @param string $plugin_id + * (optional) The plugin ID, defaults to NULL. + * + * @see \Drupal\devel\DevelDumperManager::message() + */ +function devel_message($input, $name = NULL, $type = 'status', $plugin_id = NULL) { + \Drupal::service('devel.dumper')->message($input, $name, $type, $plugin_id); +} + +/** + * Logs a variable to a drupal_debug.txt in the site's temp directory. + * + * Wrapper for DevelDumperManager::debug(). + * + * @param mixed $input + * The variable to log to the drupal_debug.txt log file. + * @param string $name + * (optional) If set, a label to output before $data in the log file. + * @param string $plugin_id + * (optional) The plugin ID, defaults to NULL. + * + * @return null|false + * Empty if successful, FALSE if the log file could not be written. + * + * @see \Drupal\devel\DevelDumperManager::debug() + */ +function devel_debug($input, $name = NULL, $plugin_id = NULL) { + return \Drupal::service('devel.dumper')->debug($input, $name, $plugin_id); +} + +/** + * Wrapper for DevelDumperManager::dump(). + * + * Calls the http://www.firephp.org/ fb() function if it is found. + * + * @see \Drupal\devel\DevelDumperManager::dump() + */ +function dfb() { + $args = func_get_args(); + \Drupal::service('devel.dumper')->dump($args, NULL, 'firephp'); +} + +/** + * Wrapper for DevelDumperManager::dump(). + * + * Calls dfb() to output a backtrace. + * + * @see \Drupal\devel\DevelDumperManager::dump() + */ +function dfbt($label) { + \Drupal::service('devel.dumper')->dump(FirePHP::TRACE, $label, 'firephp'); +} + +if (!function_exists('dd')) { + + /** + * Wrapper for DevelDumperManager::debug(). + * + * @deprecated since Drupal 8.6.0 because symfony now has dd() as a function. + * Use the ddm() function instead. + * + * @see \Drupal\devel\DevelDumperManager::debug() + */ + function dd($data, $label = NULL) { + return \Drupal::service('devel.dumper')->debug($data, $label, 'default'); + } + +} + +if (!function_exists('ddm')) { + + /** + * Wrapper for DevelDumperManager::debug() to replace previous dd function. + * + * @see \Drupal\devel\DevelDumperManager::debug() + */ + function ddm($data, $label = NULL) { + return \Drupal::service('devel.dumper')->debug($data, $label, 'default'); + } + +} + +if (!function_exists('kint')) { + + /** + * Prints passed argument(s) using Kint debug tool. + * + * Wrapper for DevelDumperManager::dump(). + * + * @see \Drupal\devel\DevelDumperManager::dump() + */ + function kint() { + $args = func_get_args(); + if (count($args) == 1) { + // Pass a single argument directly, which works for any plug-in. + $args = $args[0]; + $name = NULL; + } + else { + // Pass an array marked with a special name. The kint plug-in expands the + // arguments and prints each separately. + $name = '__ARGS__'; + } + + return \Drupal::service('devel.dumper')->dump($args, $name, 'kint'); + } + +} + +if (!function_exists('ksm')) { + + /** + * Prints passed argument(s) to the 'message' area of the page. + * + * Wrapper for DevelDumperManager::message(). + * + * @see \Drupal\devel\DevelDumperManager::message() + */ + function ksm() { + $args = func_get_args(); + if (count($args) == 1) { + // Pass a single argument directly, which works for any plug-in. + $args = $args[0]; + $name = NULL; + } + else { + // Pass an array marked with a special name. The kint plug-in expands the + // arguments and prints each separately. + $name = '__ARGS__'; + } + + return \Drupal::service('devel.dumper')->message($args, $name, MessengerInterface::TYPE_STATUS, 'kint'); + } + +} + +/** + * Wrapper for DevelDumperManager::message(). + * + * Prints a variable to the 'message' area of the page. + * + * Uses Drupal\Core\Messenger\MessengerInterface::addMessage() + * + * @param mixed $input + * An arbitrary value to output. + * @param string $name + * Optional name for identifying the output. + * @param string $type + * Optional message type see MessengerInterface, defaults to TYPE_STATUS. + * + * @return input + * The unaltered input value. + * + * @see \Drupal\devel\DevelDumperManager::message() + */ +function dpm($input, $name = NULL, $type = MessengerInterface::TYPE_STATUS) { + \Drupal::service('devel.dumper')->message($input, $name, $type); + return $input; +} + +/** + * Wrapper for DevelDumperManager::message(). + * + * Displays a Variable::export() variable to the 'message' area of the page. + * + * Uses Drupal\Core\Messenger\MessengerInterface::addMessage() + * + * @param $input + * An arbitrary value to output. + * @param string $name + * Optional name for identifying the output. + * + * @return input + * The unaltered input value. + * + * @see \Drupal\devel\DevelDumperManager::message() + */ +function dvm($input, $name = NULL) { + \Drupal::service('devel.dumper')->message($input, $name, MessengerInterface::TYPE_STATUS, 'drupal_variable'); + return $input; +} + +/** + * An alias for dpm(), for historic reasons. + */ +function dsm($input, $name = NULL) { + return dpm($input, $name); +} + +/** + * Wrapper for DevelDumperManager::dumpOrExport(). + * + * An alias for the devel.dumper service. Saves carpal tunnel syndrome. + * + * @see \Drupal\devel\DevelDumperManager::dumpOrExport() + */ +function dpr($input, $export = FALSE, $name = NULL) { + return \Drupal::service('devel.dumper')->dumpOrExport($input, $name, $export, 'default'); +} + +/** + * Wrapper for DevelDumperManager::dumpOrExport(). + * + * An alias for devel_dump(). Saves carpal tunnel syndrome. + * + * @see \Drupal\devel\DevelDumperManager::dumpOrExport() + */ +function kpr($input, $export = FALSE, $name = NULL) { + return \Drupal::service('devel.dumper')->dumpOrExport($input, $name, $export); +} + +/** + * Wrapper for DevelDumperManager::dumpOrExport(). + * + * Like dpr(), but uses Variable::export() instead. + * + * @see \Drupal\devel\DevelDumperManager::dumpOrExport() + */ +function dvr($input, $export = FALSE, $name = NULL) { + return \Drupal::service('devel.dumper')->dumpOrExport($input, $name, $export, 'drupal_variable'); +} + +/** + * Prints the arguments passed into the current function. + */ +function dargs($always = TRUE) { + static $printed; + if ($always || !$printed) { + $bt = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); + print kpr($bt[1]['args'], TRUE); + $printed = TRUE; + } +} + +/** + * Prints a SQL string from a DBTNG Select object. Includes quoted arguments. + * + * @param object $query + * An object that implements the SelectInterface interface. + * @param bool $return + * Whether to return the string. Default is FALSE, meaning to print it + * and return $query instead. + * @param string $name + * Optional name for identifying the output. + * + * @return object|string + * The $query object, or the query string if $return was TRUE. + */ +function dpq($query, $return = FALSE, $name = NULL) { + if (\Drupal::currentUser()->hasPermission('access devel information')) { + if (method_exists($query, 'preExecute')) { + $query->preExecute(); + } + $sql = (string) $query; + $quoted = []; + $database = \Drupal::database(); + foreach ((array) $query->arguments() as $key => $val) { + $quoted[$key] = is_null($val) ? 'NULL' : $database->quote($val); + } + $sql = strtr($sql, $quoted); + if ($return) { + return $sql; + } + dpm($sql, $name); + } + return ($return ? NULL : $query); +} + +/** + * Prints a renderable array element to the screen using kprint_r(). + * + * #pre_render and/or #post_render pass-through callback for kprint_r(). + * + * @todo Investigate appending to #suffix. + * @todo Investigate label derived from #id, #title, #name, and #theme. + */ +function devel_render() { + $args = func_get_args(); + // #pre_render and #post_render pass the rendered $element as last argument. + kpr(end($args)); + // #pre_render and #post_render expect the first argument to be returned. + return reset($args); +} + +/** + * Prints the function call stack. + * + * @param $return + * Pass TRUE to return the formatted backtrace rather than displaying it in + * the browser via kprint_r(). + * @param $pop + * How many items to pop from the top of the stack; useful when calling from + * an error handler. + * @param $options + * Options to pass on to PHP's debug_backtrace(). + * + * @return string|null + * The formatted backtrace, if requested, or NULL. + * + * @see http://php.net/manual/en/function.debug-backtrace.php + */ +function ddebug_backtrace($return = FALSE, $pop = 0, $options = DEBUG_BACKTRACE_PROVIDE_OBJECT) { + if (\Drupal::currentUser()->hasPermission('access devel information')) { + $backtrace = debug_backtrace($options); + while ($pop-- > 0) { + array_shift($backtrace); + } + $counter = count($backtrace); + $path = $backtrace[$counter - 1]['file']; + $path = substr($path, 0, strlen($path) - 10); + $paths[$path] = strlen($path) + 1; + $paths[DRUPAL_ROOT] = strlen(DRUPAL_ROOT) + 1; + $nbsp = "\xC2\xA0"; + + // Show message if error_level is ERROR_REPORTING_DISPLAY_SOME or higher. + // (This is Drupal's error_level, which is different from $error_level, + // and we purposely ignore the difference between _SOME and _ALL, + // see #970688!) + if (\Drupal::config('system.logging')->get('error_level') != 'hide') { + while (!empty($backtrace)) { + $call = []; + if (isset($backtrace[0]['file'])) { + $call['file'] = $backtrace[0]['file']; + foreach ($paths as $path => $len) { + if (strpos($backtrace[0]['file'], $path) === 0) { + $call['file'] = substr($backtrace[0]['file'], $len); + } + } + $call['file'] .= ':' . $backtrace[0]['line']; + } + if (isset($backtrace[1])) { + if (isset($backtrace[1]['class'])) { + $function = $backtrace[1]['class'] . $backtrace[1]['type'] . $backtrace[1]['function'] . '()'; + } + else { + $function = $backtrace[1]['function'] . '()'; + } + $backtrace[1] += ['args' => []]; + foreach ($backtrace[1]['args'] as $key => $value) { + $call['args'][$key] = $value; + } + } + else { + $function = 'main()'; + $call['args'] = $_GET; + } + $nicetrace[($counter <= 10 ? $nbsp : '') . --$counter . ': ' . $function] = $call; + array_shift($backtrace); + } + if ($return) { + return $nicetrace; + } + kpr($nicetrace); + } + } +} + +/** + * Implements hook_form_FORM_ID_alter(). + * + * Adds mouse-over hints on the Permissions page to display + * language-independent machine names and module base names. + * + * @see \Drupal\user\Form\UserPermissionsForm::buildForm() + */ +function devel_form_user_admin_permissions_alter(&$form, FormStateInterface $form_state) { + if (\Drupal::currentUser()->hasPermission('access devel information') && \Drupal::config('devel.settings')->get('raw_names')) { + foreach (Element::children($form['permissions']) as $key) { + if (isset($form['permissions'][$key][0])) { + $form['permissions'][$key][0]['#wrapper_attributes']['title'] = $key; + } + elseif (isset($form['permissions'][$key]['description'])) { + $form['permissions'][$key]['description']['#wrapper_attributes']['title'] = $key; + } + } + } +} + +/** + * Implements hook_form_FORM_ID_alter(). + * + * Adds mouse-over hints on the Modules page to display module base names. + * + * @see \Drupal\system\Form\ModulesListForm::buildForm() + * @see theme_system_modules_details() + */ +function devel_form_system_modules_alter(&$form, FormStateInterface $form_state) { + if (\Drupal::currentUser()->hasPermission('access devel information') && \Drupal::config('devel.settings')->get('raw_names', FALSE) && isset($form['modules']) && is_array($form['modules'])) { + foreach (Element::children($form['modules']) as $group) { + if (is_array($form['modules'][$group])) { + foreach (Element::children($form['modules'][$group]) as $key) { + if (isset($form['modules'][$group][$key]['name']['#markup'])) { + $form['modules'][$group][$key]['name']['#markup'] = '' . $form['modules'][$group][$key]['name']['#markup'] . ''; + } + } + } + } + } +} + +/** + * Implements hook_query_TAG_alter(). + * + * Makes debugging entity query much easier. + * + * Example usage: + * @code + * $query = \Drupal::entityQuery('node'); + * $query->condition('status', NODE_PUBLISHED); + * $query->addTag('debug'); + * $query->execute(); + * @endcode + */ +function devel_query_debug_alter(AlterableInterface $query) { + if (!$query->hasTag('debug-semaphore')) { + $query->addTag('debug-semaphore'); + dpq($query); + } +} diff --git a/web/modules/contrib/devel/devel.permissions.yml b/web/modules/contrib/devel/devel.permissions.yml new file mode 100644 index 000000000..e32ad8c11 --- /dev/null +++ b/web/modules/contrib/devel/devel.permissions.yml @@ -0,0 +1,9 @@ +access devel information: + description: 'View developer output like variable printouts, query log, etc.' + title: 'Access developer information' + restrict access: TRUE + +switch users: + title: 'Switch users' + description: 'Become any user on the site with just a click.' + restrict access: TRUE diff --git a/web/modules/contrib/devel/devel.routing.yml b/web/modules/contrib/devel/devel.routing.yml new file mode 100644 index 000000000..80be9c54f --- /dev/null +++ b/web/modules/contrib/devel/devel.routing.yml @@ -0,0 +1,288 @@ +devel.admin_settings: + path: '/admin/config/development/devel' + defaults: + _form: '\Drupal\devel\Form\SettingsForm' + _title: 'Devel settings' + requirements: + _permission: 'administer site configuration' + +devel.toolbar.settings_form: + path: '/admin/config/development/devel/toolbar' + defaults: + _form: '\Drupal\devel\Form\ToolbarSettingsForm' + _title: 'Devel Toolbar Settings' + requirements: + _permission: 'administer site configuration' + _module_dependencies: 'toolbar' + +devel.reinstall: + path: '/devel/reinstall' + defaults: + _form: '\Drupal\devel\Form\DevelReinstall' + _title: 'Reinstall modules' + options: + _admin_route: TRUE + requirements: + _permission: 'administer site configuration' + +devel.menu_rebuild: + path: '/devel/menu/reset' + defaults: + _form: '\Drupal\devel\Form\RouterRebuildConfirmForm' + _title: 'Rebuild router' + options: + _admin_route: TRUE + requirements: + _permission: 'administer site configuration' + +devel.configs_list: + path: '/devel/config/{filter}' + options: + _admin_route: TRUE + defaults: + _form: '\Drupal\devel\Form\ConfigsList' + _title: 'Config editor' + filter: '' + requirements: + _permission: 'administer site configuration' + +devel.config_edit: + path: '/devel/config/edit/{config_name}' + defaults: + _form: '\Drupal\devel\Form\ConfigEditor' + _title: 'Edit configuration object: @config_name' + options: + _admin_route: TRUE + requirements: + _permission: 'administer site configuration' + +devel.config_delete: + path: '/devel/config/delete/{config_name}' + defaults: + _form: '\Drupal\devel\Form\ConfigDeleteForm' + _title: 'Delete configuration object: @config_name' + options: + _admin_route: TRUE + requirements: + _permission: 'administer site configuration' + +devel.state_system_page: + path: '/devel/state' + defaults: + _controller: '\Drupal\devel\Controller\DevelController::stateSystemPage' + _title: 'State editor' + options: + _admin_route: TRUE + requirements: + _permission: 'access devel information' + +devel.system_state_edit: + path: '/devel/state/edit/{state_name}' + defaults: + _form: '\Drupal\devel\Form\SystemStateEdit' + _title: 'Edit state variable: @state_name' + options: + _admin_route: TRUE + requirements: + _permission: 'administer site configuration' + +devel.theme_registry: + path: '/devel/theme/registry' + defaults: + _controller: '\Drupal\devel\Controller\DevelController::themeRegistry' + _title: 'Theme registry' + options: + _admin_route: TRUE + requirements: + _permission: 'access devel information' + +devel.field_info_page: + path: '/devel/field/info' + defaults: + _controller: '\Drupal\devel\Controller\DevelController::fieldInfoPage' + _title: 'Field info' + options: + _admin_route: TRUE + requirements: + _permission: 'access devel information' + +devel.session: + path: '/devel/session' + defaults: + _controller: '\Drupal\devel\Controller\DevelController::session' + _title: 'Session viewer' + options: + _admin_route: TRUE + requirements: + _permission: 'access devel information' + +devel.switch: + path: '/devel/switch/{name}' + defaults: + _controller: '\Drupal\devel\Controller\SwitchUserController::switchUser' + _title: 'Switch user' + name: '' + options: + _admin_route: TRUE + requirements: + _permission: 'switch users' + _csrf_token: 'TRUE' + +devel.cache_clear: + path: '/devel/cache/clear' + defaults: + _controller: '\Drupal\devel\Controller\DevelController::cacheClear' + _title: 'Clear cache' + options: + _admin_route: TRUE + requirements: + _permission: 'access devel information' + _csrf_token: 'TRUE' + +devel.run_cron: + path: '/devel/run-cron' + defaults: + _controller: '\Drupal\system\CronController::runManually' + _title: 'Run cron' + options: + _admin_route: TRUE + requirements: + _permission: 'administer site configuration' + _csrf_token: 'TRUE' + +# Container info +devel.container_info.service: + path: '/devel/container/service' + defaults: + _controller: '\Drupal\devel\Controller\ContainerInfoController::serviceList' + _title: 'Container services' + options: + _admin_route: TRUE + requirements: + _permission: 'access devel information' + +devel.container_info.service.detail: + path: '/devel/container/service/{service_id}' + defaults: + _controller: '\Drupal\devel\Controller\ContainerInfoController::serviceDetail' + _title: 'Service @service_id detail' + options: + _admin_route: TRUE + requirements: + _permission: 'access devel information' + +devel.container_info.parameter: + path: '/devel/container/parameter' + defaults: + _controller: '\Drupal\devel\Controller\ContainerInfoController::parameterList' + _title: 'Container parameters' + options: + _admin_route: TRUE + requirements: + _permission: 'access devel information' + +devel.container_info.parameter.detail: + path: '/devel/container/parameter/{parameter_name}' + defaults: + _controller: '\Drupal\devel\Controller\ContainerInfoController::parameterDetail' + _title: 'Parameter @parameter_name value' + options: + _admin_route: TRUE + requirements: + _permission: 'access devel information' + +# Route info +devel.route_info: + path: '/devel/routes' + defaults: + _controller: '\Drupal\devel\Controller\RouteInfoController::routeList' + _title: 'Routes' + options: + _admin_route: TRUE + requirements: + _permission: 'access devel information' + +devel.route_info.item: + path: '/devel/routes/item' + defaults: + _controller: '\Drupal\devel\Controller\RouteInfoController::routeDetail' + _title: 'Route detail' + options: + _admin_route: TRUE + requirements: + _permission: 'access devel information' + +# Event info +devel.event_info: + path: '/devel/events' + defaults: + _controller: '\Drupal\devel\Controller\EventInfoController::eventList' + _title: 'Events' + options: + _admin_route: TRUE + requirements: + _permission: 'access devel information' + +# Layouts info +devel.layout_info: + path: '/devel/layouts' + defaults: + _controller: '\Drupal\devel\Controller\LayoutInfoController::layoutInfoPage' + _title: 'Layouts' + options: + _admin_route: TRUE + requirements: + _permission: 'access devel information' + _module_dependencies: 'layout_discovery' + +# Element info +devel.elements_page: + path: '/devel/elements' + defaults: + _controller: '\Drupal\devel\Controller\ElementInfoController::elementList' + _title: 'Element Info' + options: + _admin_route: TRUE + requirements: + _permission: 'access devel information' + +devel.elements_page.detail: + path: '/devel/elements/{element_name}' + defaults: + _controller: '\Drupal\devel\Controller\ElementInfoController::elementDetail' + _title: 'Element @element_name' + options: + _admin_route: TRUE + requirements: + _permission: 'access devel information' + +# Entity type info +devel.entity_info_page: + path: '/devel/entity/info' + defaults: + _controller: '\Drupal\devel\Controller\EntityTypeInfoController::entityTypeList' + _title: 'Entity info' + options: + _admin_route: TRUE + requirements: + _permission: 'access devel information' + +devel.entity_info_page.detail: + path: '/devel/entity/info/{entity_type_id}' + defaults: + _controller: '\Drupal\devel\Controller\EntityTypeInfoController::entityTypeDetail' + _title: 'Entity type @entity_type_id' + options: + _admin_route: TRUE + requirements: + _permission: 'access devel information' + +devel.entity_info_page.fields: + path: '/devel/entity/fields/{entity_type_id}' + defaults: + _controller: '\Drupal\devel\Controller\EntityTypeInfoController::entityTypeFields' + _title: 'Entity fields @entity_type_id' + options: + _admin_route: TRUE + requirements: + _permission: 'access devel information' diff --git a/web/modules/contrib/devel/devel.services.yml b/web/modules/contrib/devel/devel.services.yml new file mode 100644 index 000000000..2e74e1ad8 --- /dev/null +++ b/web/modules/contrib/devel/devel.services.yml @@ -0,0 +1,32 @@ +services: + devel.error_subscriber: + class: Drupal\devel\EventSubscriber\ErrorHandlerSubscriber + arguments: ['@current_user'] + tags: + - { name: event_subscriber } + + devel.theme_rebuild_subscriber: + class: Drupal\devel\EventSubscriber\ThemeInfoRebuildSubscriber + arguments: ['@config.factory', '@current_user', '@theme_handler'] + tags: + - { name: event_subscriber } + + devel.route_subscriber: + class: Drupal\devel\Routing\RouteSubscriber + arguments: ['@entity_type.manager'] + tags: + - { name: event_subscriber } + + plugin.manager.devel_dumper: + class: Drupal\devel\DevelDumperPluginManager + parent: default_plugin_manager + + devel.dumper: + class: Drupal\devel\DevelDumperManager + arguments: ['@config.factory', '@current_user', '@plugin.manager.devel_dumper'] + + devel.twig.debug_extension: + class: Drupal\devel\Twig\Extension\Debug + arguments: ['@devel.dumper'] + tags: + - { name: twig.extension } diff --git a/web/modules/contrib/devel/devel_generate/README.md b/web/modules/contrib/devel/devel_generate/README.md new file mode 100644 index 000000000..8ed10c120 --- /dev/null +++ b/web/modules/contrib/devel/devel_generate/README.md @@ -0,0 +1,57 @@ +[[_TOC_]] + +This module may be used to create entities that contain sample content. This is +useful when showing off your site to a client, for example. Even if the content +is not yet available, the site can show its look and feel and behavior. + +The sample entities may be created via the Web or via the included Drush commands. + +#### Recommended Modules + +- [Devel Images Provider](http://drupal.org/project/devel_image_provider) allows to configure external providers for images. + +#### Custom plugins + +This module creates the _DevelGenerate_ plugin type. + +All you need to do to provide a new instance for DevelGenerate plugin type +is to create your class extending `DevelGenerateBase` and following these steps: + +1. Declare your plugin with annotations: + ```` + /** + * Provides a ExampleDevelGenerate plugin. + * + * @DevelGenerate( + * id = "example", + * label = @Translation("example"), + * description = @Translation("Generate a given number of example elements."), + * url = "example", + * permission = "administer example", + * settings = { + * "num" = 50, + * "kill" = FALSE, + * "another_property" = "default_value" + * } + * ) + */ + ```` +1. Implement the `settingsForm` method to create a form using the properties +from the annotations. +1. Implement the `handleDrushParams` method. It should return an array of +values. +1. Implement the `generateElements` method. You can write here your business +logic using the array of values. + +#### Notes + +- You can alter existing properties for every plugin by implementing +`hook_devel_generate_info_alter`. +- DevelGenerateBaseInterface details base wrapping methods that most +DevelGenerate implementations will want to directly inherit from +`Drupal\devel_generate\DevelGenerateBase`. +- To give support for a new field type the field type base class should properly +implement `\Drupal\Core\Field\FieldItemInterface::generateSampleValue()`. +Devel Generate automatically uses the values returned by this method during the +generate process for generating placeholder field values. For more information +see: https://api.drupal.org/api/drupal/core!lib!Drupal!Core!Field!FieldItemInterface.php/function/FieldItemInterface::generateSampleValue diff --git a/web/modules/contrib/devel/devel_generate/composer.json b/web/modules/contrib/devel/devel_generate/composer.json new file mode 100644 index 000000000..4c32c156c --- /dev/null +++ b/web/modules/contrib/devel/devel_generate/composer.json @@ -0,0 +1,52 @@ +{ + "name": "drupal/devel_generate", + "description": "Generate dummy users, nodes, menus, taxonomy terms...", + "type": "drupal-module", + "homepage": "http://drupal.org/project/devel", + "authors": [ + { + "name": "Moshe Weitzman", + "email": "weitzman@tejasa.com", + "homepage": "https://github.com/weitzman", + "role": "Maintainer" + }, + { + "name": "Hans Salvisberg", + "email": "drupal@salvisberg.com", + "homepage": "https://www.drupal.org/u/salvis", + "role": "Maintainer" + }, + { + "name": "Luca Lusso", + "homepage": "https://www.drupal.org/u/lussoluca", + "role": "Maintainer" + }, + { + "name": "Marco (willzyx)", + "homepage": "https://www.drupal.org/u/willzyx", + "role": "Maintainer" + }, + { + "name": "See contributors", + "homepage": "https://www.drupal.org/node/3236/committers" + } + ], + "support": { + "issues": "http://drupal.org/project/devel", + "irc": "irc://irc.freenode.org/drupal-contribute", + "source": "http://cgit.drupalcode.org/devel" + }, + "license": "GPL-2.0-or-later", + "minimum-stability": "dev", + "require": {}, + "suggest": { + "drupal/realistic_dummy_content": "Generate realistic demo content with Devel's devel_generate module." + }, + "extra": { + "drush": { + "services": { + "drush.services.yml": "^9 || ^10" + } + } + } +} diff --git a/web/modules/contrib/devel/devel_generate/devel_generate.batch.inc b/web/modules/contrib/devel/devel_generate/devel_generate.batch.inc new file mode 100644 index 000000000..09b74c15e --- /dev/null +++ b/web/modules/contrib/devel/devel_generate/devel_generate.batch.inc @@ -0,0 +1,34 @@ +$method($vars, $context); +} + +/** + * Standard finish batch function. + */ +function devel_generate_batch_finished($success, $results, $operations) { + + if ($success) { + if (!empty($results['num_translations'])) { + $message = t('Finished @num elements and @num_translations translations created successfully.', ['@num' => $results['num'], '@num_translations' => $results['num_translations']]); + } + else { + $message = t('Finished @num elements created successfully.', ['@num' => $results['num']]); + } + } + else { + $message = t('Finished with an error.'); + } + \Drupal::messenger()->addMessage($message); +} diff --git a/web/modules/contrib/devel/devel_generate/devel_generate.info.yml b/web/modules/contrib/devel/devel_generate/devel_generate.info.yml new file mode 100644 index 000000000..1286f3763 --- /dev/null +++ b/web/modules/contrib/devel/devel_generate/devel_generate.info.yml @@ -0,0 +1,13 @@ +type: module +name: 'Devel Generate' +description: 'Generate dummy users, nodes, menus, taxonomy terms...' +package: Development +core_version_requirement: ^8.8 || ^9 +php: 7.2 +tags: + - developer + +# Information added by Drupal.org packaging script on 2020-12-31 +version: '4.1.1' +project: 'devel' +datestamp: 1609419530 diff --git a/web/modules/contrib/devel/devel_generate/devel_generate.module b/web/modules/contrib/devel/devel_generate/devel_generate.module new file mode 100644 index 000000000..d825ec92f --- /dev/null +++ b/web/modules/contrib/devel/devel_generate/devel_generate.module @@ -0,0 +1,177 @@ +getDefinitions(); + foreach ($devel_generate_plugins as $id => $plugin) { + $label = $plugin['label']; + $links["devel_generate.$id"] = [ + 'title' => new TranslatableMarkup("Generate @label", ['@label' => $label]), + 'parent' => 'devel_generate.admin_config_generate', + 'description' => $plugin['description'], + 'route_name' => "devel_generate.$id", + 'provider' => 'devel_generate', + ]; + } + + // Store the basic link info for repeated use. Each of the three actual links + // require subtle variations on this. + $basics = [ + 'title' => new TranslatableMarkup('Generate'), + 'description' => new TranslatableMarkup('Generate realistic items (content, users, menus, etc) to assist your site development and testing.'), + 'route_name' => 'devel_generate.admin_config_generate', + 'provider' => 'devel_generate', + ]; + + // Define a separate group on admin/config page, so that 'Generate' has its + // own block with all the generate links. + $links['devel_generate.admin_config_generate'] = [ + 'parent' => 'system.admin_config', + // The main development group has weight -10 in system.links.menu.yml so use + // -9 here as this block should be near but just after it on the page. + 'weight' => -9, + ] + $basics; + + // Add a link in the main development group, to allow direct access to the + // the Generate page and to make the back breadcrumb more useful. + $links['devel_generate.generate'] = [ + 'title' => new TranslatableMarkup('Devel generate'), + 'parent' => 'system.admin_config_development', + ] + $basics; + + // Define a top-level link (with no parent) in the 'devel' menu. This also + // means that it will be available in the devel admin toolbar. + $links['devel_generate.generate2'] = ['menu_name' => 'devel'] + $basics; + +} + +/** + * Implements hook_entity_insert(). + * + * Inserts nodes properly based on generation options. + * + * @param \Drupal\Core\Entity\EntityInterface $entity + * The base node created on submit. Inspects $node->devel_generate. + * + * @todo Can more of this processing move into develGenerateContentAddNode() ? + * Adding url alias is now moved. + */ +function devel_generate_entity_insert(EntityInterface $entity) { + if ($entity->getEntityTypeId() != 'node' || !isset($entity->devel_generate)) { + return; + } + /* @var \Drupal\node\NodeInterface $entity */ + $results = $entity->devel_generate; + + if (!empty($results['max_comments'])) { + foreach ($entity->getFieldDefinitions() as $field_name => $field_definition) { + if ($field_definition->getType() == 'comment' && $entity->get($field_name)->status == CommentItemInterface::OPEN) { + // Add comments for each comment field on entity. + devel_generate_add_comments($entity, $field_definition, $results['users'], $results['max_comments'], $results['title_length']); + } + } + } + + // Add node statistics. + if (!empty($results['add_statistics']) && \Drupal::moduleHandler()->moduleExists('statistics')) { + devel_generate_add_statistics($entity); + } +} + +/** + * Create comments and add them to a node. + * + * @param \Drupal\node\NodeInterface $node + * Node to add comments to. + * @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition + * The field storage definition. + * @param array $users + * Array of users to assign comment authors. + * @param int $max_comments + * Max number of comments to generate per node. + * @param int $title_length + * Max length of the title of the comments. + */ +function devel_generate_add_comments(NodeInterface $node, FieldDefinitionInterface $field_definition, $users, $max_comments, $title_length = 8) { + $parents = []; + $field_name = $field_definition->getName(); + $num_comments = mt_rand(0, $max_comments); + for ($i = 1; $i <= $num_comments; $i++) { + switch ($i % 3) { + case 0: + // No parent. + case 1: + // Top level parent. + $parents = \Drupal::entityQuery('comment') + ->condition('pid', 0) + ->condition('entity_id', $node->id()) + ->condition('entity_type', 'node') + ->condition('field_name', $field_name) + ->range(0, 1) + ->execute(); + break; + + case 2: + // Non top level parent. + $parents = \Drupal::entityQuery('comment') + ->condition('pid', 0, '>') + ->condition('entity_id', $node->id()) + ->condition('entity_type', 'node') + ->condition('field_name', $field_name) + ->range(0, 1) + ->execute(); + break; + } + $random = new Random(); + $stub = [ + 'entity_type' => $node->getEntityTypeId(), + 'entity_id' => $node->id(), + 'field_name' => $field_name, + 'name' => 'devel generate', + 'mail' => 'devel_generate@example.com', + 'timestamp' => mt_rand($node->getCreatedTime(), \Drupal::time()->getRequestTime()), + 'subject' => substr($random->sentences(mt_rand(1, $title_length), TRUE), 0, 63), + 'uid' => $users[array_rand($users)], + 'langcode' => $node->language()->getId(), + ]; + if ($parents) { + $stub['pid'] = current($parents); + } + $comment = \Drupal::entityTypeManager()->getStorage('comment')->create($stub); + + // Populate all core fields on behalf of field.module. + DevelGenerateBase::populateFields($comment); + $comment->save(); + } +} + +/** + * Generate statistics information for a node. + * + * @param \Drupal\node\NodeInterface $node + * A node object. + */ +function devel_generate_add_statistics(NodeInterface $node) { + $statistic = [ + 'nid' => $node->id(), + 'totalcount' => mt_rand(0, 500), + 'timestamp' => \Drupal::time()->getRequestTime() - mt_rand(0, $node->getCreatedTime()), + ]; + $statistic['daycount'] = mt_rand(0, $statistic['totalcount']); + $database = \Drupal::database(); + $database->insert('node_counter')->fields($statistic)->execute(); +} diff --git a/web/modules/contrib/devel/devel_generate/devel_generate.permissions.yml b/web/modules/contrib/devel/devel_generate/devel_generate.permissions.yml new file mode 100644 index 000000000..10f9479e5 --- /dev/null +++ b/web/modules/contrib/devel/devel_generate/devel_generate.permissions.yml @@ -0,0 +1,5 @@ +administer devel_generate: + title: 'Administer devel_generate' + +permission_callbacks: + - \Drupal\devel_generate\DevelGeneratePermissions::permissions diff --git a/web/modules/contrib/devel/devel_generate/devel_generate.routing.yml b/web/modules/contrib/devel/devel_generate/devel_generate.routing.yml new file mode 100644 index 000000000..fe3199f17 --- /dev/null +++ b/web/modules/contrib/devel/devel_generate/devel_generate.routing.yml @@ -0,0 +1,2 @@ +route_callbacks: + - '\Drupal\devel_generate\Routing\DevelGenerateRoutes::routes' diff --git a/web/modules/contrib/devel/devel_generate/devel_generate.services.yml b/web/modules/contrib/devel/devel_generate/devel_generate.services.yml new file mode 100644 index 000000000..16025e1da --- /dev/null +++ b/web/modules/contrib/devel/devel_generate/devel_generate.services.yml @@ -0,0 +1,4 @@ +services: + plugin.manager.develgenerate: + class: Drupal\devel_generate\DevelGeneratePluginManager + parent: default_plugin_manager diff --git a/web/modules/contrib/devel/devel_generate/drush.services.yml b/web/modules/contrib/devel/devel_generate/drush.services.yml new file mode 100644 index 000000000..11c8e9ca4 --- /dev/null +++ b/web/modules/contrib/devel/devel_generate/drush.services.yml @@ -0,0 +1,6 @@ +services: + develgenerate.commands: + class: Drupal\devel_generate\Commands\DevelGenerateCommands + arguments: ['@plugin.manager.develgenerate'] + tags: + - { name: drush.command } diff --git a/web/modules/contrib/devel/devel_generate/src/Annotation/DevelGenerate.php b/web/modules/contrib/devel/devel_generate/src/Annotation/DevelGenerate.php new file mode 100644 index 000000000..ddc5ff276 --- /dev/null +++ b/web/modules/contrib/devel/devel_generate/src/Annotation/DevelGenerate.php @@ -0,0 +1,85 @@ +setManager($manager); + } + + /** + * Get the DevelGenerate plugin manager. + * + * @return \Drupal\devel_generate\DevelGeneratePluginManager + * The DevelGenerate plugin manager. + */ + public function getManager() { + return $this->manager; + } + + /** + * Set the DevelGenerate plugin manager. + * + * @param \Drupal\devel_generate\DevelGeneratePluginManager $manager + * The DevelGenerate plugin manager. + */ + public function setManager(DevelGeneratePluginManager $manager) { + $this->manager = $manager; + } + + /** + * Get the DevelGenerate plugin instance. + * + * @return mixed + * The DevelGenerate plugin instance. + */ + public function getPluginInstance() { + return $this->pluginInstance; + } + + /** + * Set the DevelGenerate plugin instance. + * + * @param mixed $pluginInstance + * The DevelGenerate plugin instance. + */ + public function setPluginInstance($pluginInstance) { + $this->pluginInstance = $pluginInstance; + } + + /** + * Get the DevelGenerate plugin parameters. + * + * @return array + * The plugin parameters. + */ + public function getParameters() { + return $this->parameters; + } + + /** + * Set the DevelGenerate plugin parameters. + * + * @param array $parameters + * The plugin parameters. + */ + public function setParameters(array $parameters) { + $this->parameters = $parameters; + } + + /** + * Create users. + * + * @command devel-generate:users + * @aliases genu, devel-generate-users + * @pluginId user + * + * @param int $num + * Number of users to generate. + * @param array $options + * Array of options as described below. + * + * @option kill Delete all users before generating new ones. + * @option roles A comma delimited list of role IDs for new users. Don't specify 'authenticated'. + * @option pass Specify a password to be set for all generated users. + */ + public function users($num = 50, array $options = ['kill' => FALSE, 'roles' => '']) { + // @todo pass $options to the plugins. + $this->generate(); + } + + /** + * Create terms in specified vocabulary. + * + * @command devel-generate:terms + * @aliases gent, devel-generate-terms + * @pluginId term + * + * @param int $num + * Number of terms to generate. + * @param array $options + * Array of options as described below. + * + * @option kill Delete all terms in these vocabularies before generating new ones. + * @option bundles A comma-delimited list of machine names for the vocabularies where terms will be created. + * @option feedback An integer representing interval for insertion rate logging. + * @option languages A comma-separated list of language codes + * @option translations A comma-separated list of language codes for translations. + * @option min-depth The minimum depth of hierarchy for the new terms. + * @option max-depth The maximum depth of hierarchy for the new terms. + */ + public function terms($num = 50, array $options = ['kill' => FALSE, 'bundles' => NULL, 'feedback' => '1000', 'languages' => NULL, 'translations' => NULL, 'min-depth' => '1', 'max-depth' => '4']) { + $this->generate(); + } + + /** + * Create vocabularies. + * + * @command devel-generate:vocabs + * @aliases genv, devel-generate-vocabs + * @pluginId vocabulary + * @validate-module-enabled taxonomy + * + * @param int $num + * Number of vocabularies to generate. + * @param array $options + * Array of options as described below. + * + * @option kill Delete all vocabs before generating new ones. + */ + public function vocabs($num = 1, array $options = ['kill' => FALSE]) { + $this->generate(); + } + + /** + * Create menus. + * + * @command devel-generate:menus + * @aliases genm, devel-generate-menus + * @pluginId menu + * @validate-module-enabled menu_link_content + * + * @param int $number_menus + * Number of menus to generate. + * @param int $number_links + * Number of links to generate. + * @param int $max_depth + * Max link depth. + * @param int $max_width + * Max width of first level of links. + * @param array $options + * Array of options as described below. + * + * @option kill Delete any menus and menu links previously created by devel_generate before generating new ones. + */ + public function menus($number_menus = 2, $number_links = 50, $max_depth = 3, $max_width = 8, array $options = ['kill' => FALSE]) { + $this->generate(); + } + + /** + * Create content. + * + * @command devel-generate:content + * @aliases genc, devel-generate-content + * @pluginId content + * @validate-module-enabled node + * + * @param int $num + * Number of nodes to generate. + * @param int $max_comments + * Maximum number of comments to generate. + * @param array $options + * Array of options as described below. + * + * @option kill Delete all content before generating new content. + * @option bundles A comma-delimited list of content types to create. + * @option authors A comma delimited list of authors ids. Defaults to all users. + * @option feedback An integer representing interval for insertion rate logging. + * @option skip-fields A comma delimited list of fields to omit when generating random values + * @option languages A comma-separated list of language codes + * @option translations A comma-separated list of language codes for translations. + * @option add-type-label Add the content type label to the front of the node title + */ + public function content($num = 50, $max_comments = 0, array $options = ['kill' => FALSE, 'bundles' => 'page,article', 'authors' => NULL, 'feedback' => 1000, 'languages' => NULL, 'translations' => NULL, 'add-type-label' => FALSE]) { + $this->generate(); + } + + /** + * Create media items. + * + * @command devel-generate:media + * @aliases genmd, devel-generate-media + * @pluginId media + * @validate-module-enabled media + * + * @param int $num + * Number of media items to generate. + * @param array $options + * Array of options as described below. + * + * @option kill Delete all media items before generating new media. + * @option media-types A comma-delimited list of media types to create. + * @option feedback An integer representing interval for insertion rate logging. + * @option skip-fields A comma delimited list of fields to omit when generating random values. + * @option languages A comma-separated list of language codes + */ + public function media($num = 50, array $options = ['kill' => FALSE, 'media-types' => NULL, 'feedback' => 1000]) { + $this->generate(); + } + + /** + * The standard drush validate hook. + * + * @hook validate + * + * @param \Consolidation\AnnotatedCommand\CommandData $commandData + * The data sent from the drush command. + */ + public function validate(CommandData $commandData) { + $manager = $this->getManager(); + $args = $commandData->input()->getArguments(); + // The command name is the first argument but we do not need this. + array_shift($args); + /* @var DevelGenerateBaseInterface $instance */ + $instance = $manager->createInstance($commandData->annotationData()->get('pluginId'), []); + $this->setPluginInstance($instance); + $parameters = $instance->validateDrushParams($args, $commandData->input()->getOptions()); + $this->setParameters($parameters); + } + + /** + * Wrapper for calling the plugin instance generate function. + */ + public function generate() { + $instance = $this->getPluginInstance(); + $instance->generate($this->getParameters()); + } + +} diff --git a/web/modules/contrib/devel/devel_generate/src/DevelGenerateBase.php b/web/modules/contrib/devel/devel_generate/src/DevelGenerateBase.php new file mode 100644 index 000000000..30087bc27 --- /dev/null +++ b/web/modules/contrib/devel/devel_generate/src/DevelGenerateBase.php @@ -0,0 +1,307 @@ +settings)) { + $this->settings = $this->getDefaultSettings(); + } + return isset($this->settings[$key]) ? $this->settings[$key] : NULL; + } + + /** + * {@inheritdoc} + */ + public function getDefaultSettings() { + $definition = $this->getPluginDefinition(); + return $definition['settings']; + } + + /** + * {@inheritdoc} + */ + public function getSettings() { + return $this->settings; + } + + /** + * {@inheritdoc} + */ + public function settingsForm(array $form, FormStateInterface $form_state) { + return []; + } + + /** + * {@inheritdoc} + */ + public function settingsFormValidate(array $form, FormStateInterface $form_state) { + // Validation is optional. + } + + /** + * {@inheritdoc} + */ + public function generate(array $values) { + $this->generateElements($values); + $this->setMessage('Generate process complete.'); + } + + /** + * Business logic relating with each DevelGenerate plugin. + * + * @param array $values + * The input values from the settings form. + */ + protected function generateElements(array $values) { + + } + + /** + * Populate the fields on a given entity with sample values. + * + * @param \Drupal\Core\Entity\EntityInterface $entity + * The entity to be enriched with sample field values. + * + * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException + * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException + */ + public static function populateFields(EntityInterface $entity) { + $properties = [ + 'entity_type' => $entity->getEntityType()->id(), + 'bundle' => $entity->bundle(), + ]; + $field_config_storage = \Drupal::entityTypeManager()->getStorage('field_config'); + /* @var \Drupal\field\FieldConfigInterface[] $instances */ + $instances = $field_config_storage->loadByProperties($properties); + + // @todo not implemented for Drush9+. Possibly remove. + if ($skips = @$_REQUEST['skip-fields']) { + foreach (explode(',', $skips) as $skip) { + unset($instances[$skip]); + } + } + + foreach ($instances as $instance) { + $field_storage = $instance->getFieldStorageDefinition(); + $max = $cardinality = $field_storage->getCardinality(); + if ($cardinality == FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED) { + // Just an arbitrary number for 'unlimited'. + $max = rand(1, 3); + } + $field_name = $field_storage->getName(); + $entity->$field_name->generateSampleItems($max); + } + } + + /** + * {@inheritdoc} + */ + public function handleDrushParams($args) { + + } + + /** + * Set a message for either drush or the web interface. + * + * @param string $msg + * The message to display. + * @param string $type + * (optional) The message type, as defined in MessengerInterface. Defaults + * to MessengerInterface::TYPE_STATUS. + */ + protected function setMessage($msg, $type = MessengerInterface::TYPE_STATUS) { + if (function_exists('drush_log')) { + $msg = strip_tags($msg); + drush_log($msg); + } + else { + \Drupal::messenger()->addMessage($msg, $type); + } + } + + /** + * Check if a given param is a number. + * + * @param mixed $number + * The parameter to check. + * + * @return bool + * TRUE if the parameter is a number, FALSE otherwise. + */ + public static function isNumber($number) { + if ($number == NULL) { + return FALSE; + } + if (!is_numeric($number)) { + return FALSE; + } + return TRUE; + } + + /** + * Gets the entity type manager service. + * + * @return \Drupal\Core\Entity\EntityTypeManagerInterface + * The entity type manager service. + */ + protected function getEntityTypeManager() { + if (!$this->entityTypeManager) { + $this->entityTypeManager = \Drupal::entityTypeManager(); + } + return $this->entityTypeManager; + } + + /** + * Returns the random data generator. + * + * @return \Drupal\Component\Utility\Random + * The random data generator. + */ + protected function getRandom() { + if (!$this->random) { + $this->random = new Random(); + } + return $this->random; + } + + /** + * Generates a random sentence of specific length. + * + * Words are randomly selected with length from 2 up to the optional parameter + * $max_word_length. The first word is capitalised. No ending period is added. + * + * @param int $sentence_length + * The total length of the sentence, including the word-separating spaces. + * @param int $max_word_length + * (optional) Maximum length of each word. Defaults to 8. + * + * @return string + * A sentence of the required length. + */ + protected function randomSentenceOfLength($sentence_length, $max_word_length = 8) { + // Maximum word length cannot be longer than the sentence length. + $max_word_length = min($sentence_length, $max_word_length); + $words = []; + $remainder = $sentence_length; + do { + if ($remainder <= $max_word_length) { + // If near enough to the end then generate the exact length word to fit. + $next_word = $remainder; + } + else { + // Cannot fill the remaining space with one word, so choose a random + // length, short enough for a following word of at least minimum length. + $next_word = mt_rand(2, min($max_word_length, $remainder - 3)); + } + $words[] = $this->getRandom()->word($next_word); + $remainder = $remainder - $next_word - 1; + } while ($remainder > 0); + $sentence = ucfirst(implode(' ', $words)); + return $sentence; + } + + /** + * Creates the language and translation section of the form. + * + * This is used by both Content and Term generation. + * + * @param string $items + * The name of the things that are being generated - 'nodes' or 'terms'. + * + * @return array + * The language details section of the form. + */ + protected function getLanguageForm($items) { + // We always need a language, even if the language module is not installed. + $options = []; + $languages = $this->languageManager->getLanguages(LanguageInterface::STATE_CONFIGURABLE); + foreach ($languages as $langcode => $language) { + $options[$langcode] = $language->getName(); + } + + $language_module_exists = $this->moduleHandler->moduleExists('language'); + $translation_module_exists = $this->moduleHandler->moduleExists('content_translation'); + + $form['language'] = [ + '#type' => 'details', + '#title' => $this->t('Language'), + '#open' => $language_module_exists, + ]; + $form['language']['add_language'] = [ + '#type' => 'select', + '#title' => $this->t('Select the primary language(s) for @items', ['@items' => $items]), + '#multiple' => TRUE, + '#description' => $language_module_exists ? '' : $this->t('Disabled - requires Language module'), + '#options' => $options, + '#default_value' => [ + $this->languageManager->getDefaultLanguage()->getId(), + ], + '#disabled' => !$language_module_exists, + ]; + $form['language']['translate_language'] = [ + '#type' => 'select', + '#title' => $this->t('Select the language(s) for translated @items', ['@items' => $items]), + '#multiple' => TRUE, + '#description' => $translation_module_exists ? $this->t('Translated @items will be created for each language selected.', ['@items' => $items]) : $this->t('Disabled - requires Content Translation module.'), + '#options' => $options, + '#disabled' => !$translation_module_exists, + ]; + return $form; + } + + /** + * Return a language code. + * + * @param array $add_language + * Optional array of language codes from which to select one at random. + * If empty then return the site's default language. + * + * @return string + * The language code to use. + */ + protected function getLangcode(array $add_language) { + if (empty($add_language)) { + return $this->languageManager->getDefaultLanguage()->getId(); + } + return $add_language[array_rand($add_language)]; + } + +} diff --git a/web/modules/contrib/devel/devel_generate/src/DevelGenerateBaseInterface.php b/web/modules/contrib/devel/devel_generate/src/DevelGenerateBaseInterface.php new file mode 100644 index 000000000..2cd54e73f --- /dev/null +++ b/web/modules/contrib/devel/devel_generate/src/DevelGenerateBaseInterface.php @@ -0,0 +1,83 @@ +develGeneratePluginManager = $develGeneratePluginManager; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static($container->get('plugin.manager.develgenerate')); + } + + /** + * A permissions callback. + * + * @see devel_generate.permissions.yml + * + * @return array + * An array of permissions for all plugins. + */ + public function permissions() { + $devel_generate_plugins = $this->develGeneratePluginManager->getDefinitions(); + foreach ($devel_generate_plugins as $plugin) { + + $permission = $plugin['permission']; + $permissions[$permission] = [ + 'title' => $this->t('@permission', ['@permission' => $permission]), + ]; + } + + return $permissions; + } + +} diff --git a/web/modules/contrib/devel/devel_generate/src/DevelGeneratePluginManager.php b/web/modules/contrib/devel/devel_generate/src/DevelGeneratePluginManager.php new file mode 100644 index 000000000..a068f09d4 --- /dev/null +++ b/web/modules/contrib/devel/devel_generate/src/DevelGeneratePluginManager.php @@ -0,0 +1,53 @@ +alterInfo('devel_generate_info'); + $this->setCacheBackend($cache_backend, 'devel_generate_plugins'); + } + + /** + * {@inheritdoc} + */ + protected function findDefinitions() { + $definitions = []; + foreach (parent::findDefinitions() as $plugin_id => $plugin_definition) { + $plugin_available = TRUE; + foreach ($plugin_definition['dependencies'] as $module_name) { + // If a plugin defines module dependencies and at least one module is + // not installed don't make this plugin available. + if (!$this->moduleHandler->moduleExists($module_name)) { + $plugin_available = FALSE; + break; + } + } + if ($plugin_available) { + $definitions[$plugin_id] = $plugin_definition; + } + } + return $definitions; + } + +} diff --git a/web/modules/contrib/devel/devel_generate/src/Form/DevelGenerateForm.php b/web/modules/contrib/devel/devel_generate/src/Form/DevelGenerateForm.php new file mode 100644 index 000000000..0a16fff66 --- /dev/null +++ b/web/modules/contrib/devel/devel_generate/src/Form/DevelGenerateForm.php @@ -0,0 +1,113 @@ +develGenerateManager = $devel_generate_manager; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('plugin.manager.develgenerate') + ); + } + + /** + * {@inheritdoc} + */ + public function getFormId() { + return 'devel_generate_form_' . $this->getPluginIdFromRequest(); + } + + /** + * Returns the value of the param _plugin_id for the current request. + * + * @see \Drupal\devel_generate\Routing\DevelGenerateRouteSubscriber + */ + protected function getPluginIdFromRequest() { + $request = $this->getRequest(); + return $request->get('_plugin_id'); + } + + /** + * Returns a DevelGenerate plugin instance for a given plugin id. + * + * @param string $plugin_id + * The plugin_id for the plugin instance. + * + * @return \Drupal\devel_generate\DevelGenerateBaseInterface + * A DevelGenerate plugin instance. + */ + public function getPluginInstance($plugin_id) { + $instance = $this->develGenerateManager->createInstance($plugin_id, []); + return $instance; + } + + /** + * {@inheritdoc} + */ + public function buildForm(array $form, FormStateInterface $form_state) { + $plugin_id = $this->getPluginIdFromRequest(); + $instance = $this->getPluginInstance($plugin_id); + $form = $instance->settingsForm($form, $form_state); + $form['actions'] = ['#type' => 'actions']; + $form['actions']['submit'] = [ + '#type' => 'submit', + '#value' => $this->t('Generate'), + '#button_type' => 'primary', + ]; + + return $form; + } + + /** + * {@inheritdoc} + */ + public function validateForm(array &$form, FormStateInterface $form_state) { + $plugin_id = $this->getPluginIdFromRequest(); + $instance = $this->getPluginInstance($plugin_id); + $instance->settingsFormValidate($form, $form_state); + } + + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, FormStateInterface $form_state) { + try { + $plugin_id = $this->getPluginIdFromRequest(); + $instance = $this->getPluginInstance($plugin_id); + $instance->generate($form_state->getValues()); + } + catch (\Exception $e) { + $this->logger('DevelGenerate', $this->t('Failed to generate elements due to "%error".', ['%error' => $e->getMessage()])); + $this->messenger()->addMessage($this->t('Failed to generate elements due to "%error".', ['%error' => $e->getMessage()])); + } + } + +} diff --git a/web/modules/contrib/devel/devel_generate/src/Plugin/DevelGenerate/ContentDevelGenerate.php b/web/modules/contrib/devel/devel_generate/src/Plugin/DevelGenerate/ContentDevelGenerate.php new file mode 100644 index 000000000..d22d70f63 --- /dev/null +++ b/web/modules/contrib/devel/devel_generate/src/Plugin/DevelGenerate/ContentDevelGenerate.php @@ -0,0 +1,728 @@ +moduleHandler = $module_handler; + $this->nodeStorage = $node_storage; + $this->nodeTypeStorage = $node_type_storage; + $this->userStorage = $user_storage; + $this->commentManager = $comment_manager; + $this->languageManager = $language_manager; + $this->contentTranslationManager = $content_translation_manager; + $this->urlGenerator = $url_generator; + $this->aliasStorage = $alias_storage; + $this->dateFormatter = $date_formatter; + $this->time = $time; + $this->database = $database; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + $entity_type_manager = $container->get('entity_type.manager'); + return new static( + $configuration, $plugin_id, $plugin_definition, + $entity_type_manager->getStorage('node'), + $entity_type_manager->getStorage('node_type'), + $entity_type_manager->getStorage('user'), + $container->get('module_handler'), + $container->has('comment.manager') ? $container->get('comment.manager') : NULL, + $container->get('language_manager'), + $container->has('content_translation.manager') ? $container->get('content_translation.manager') : NULL, + $container->get('url_generator'), + $entity_type_manager->getStorage('path_alias'), + $container->get('date.formatter'), + $container->get('datetime.time'), + $container->get('database') + ); + } + + /** + * {@inheritdoc} + */ + public function settingsForm(array $form, FormStateInterface $form_state) { + $types = $this->nodeTypeStorage->loadMultiple(); + + if (empty($types)) { + $create_url = $this->urlGenerator->generateFromRoute('node.type_add'); + $this->setMessage($this->t('You do not have any content types that can be generated. Go create a new content type', [':create-type' => $create_url]), 'error', FALSE); + return; + } + + $options = []; + + foreach ($types as $type) { + $options[$type->id()] = [ + 'type' => ['#markup' => $type->label()], + ]; + if ($this->commentManager) { + $comment_fields = $this->commentManager->getFields('node'); + $map = [$this->t('Hidden'), $this->t('Closed'), $this->t('Open')]; + + $fields = []; + foreach ($comment_fields as $field_name => $info) { + // Find all comment fields for the bundle. + if (in_array($type->id(), $info['bundles'])) { + $instance = FieldConfig::loadByName('node', $type->id(), $field_name); + $default_value = $instance->getDefaultValueLiteral(); + $default_mode = reset($default_value); + $fields[] = new FormattableMarkup('@field: @state', [ + '@field' => $instance->label(), + '@state' => $map[$default_mode['status']], + ]); + } + } + // @todo Refactor display of comment fields. + if (!empty($fields)) { + $options[$type->id()]['comments'] = [ + 'data' => [ + '#theme' => 'item_list', + '#items' => $fields, + ], + ]; + } + else { + $options[$type->id()]['comments'] = $this->t('No comment fields'); + } + } + } + + $header = [ + 'type' => $this->t('Content type'), + ]; + if ($this->commentManager) { + $header['comments'] = [ + 'data' => $this->t('Comments'), + 'class' => [RESPONSIVE_PRIORITY_MEDIUM], + ]; + } + + $form['node_types'] = [ + '#type' => 'tableselect', + '#header' => $header, + '#options' => $options, + ]; + + $form['kill'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Delete all content in these content types before generating new content.'), + '#default_value' => $this->getSetting('kill'), + ]; + $form['num'] = [ + '#type' => 'number', + '#title' => $this->t('How many nodes would you like to generate?'), + '#default_value' => $this->getSetting('num'), + '#required' => TRUE, + '#min' => 0, + ]; + + $options = [1 => $this->t('Now')]; + foreach ([3600, 86400, 604800, 2592000, 31536000] as $interval) { + $options[$interval] = $this->dateFormatter->formatInterval($interval, 1) . ' ' . $this->t('ago'); + } + $form['time_range'] = [ + '#type' => 'select', + '#title' => $this->t('How far back in time should the nodes be dated?'), + '#description' => $this->t('Node creation dates will be distributed randomly from the current time, back to the selected time.'), + '#options' => $options, + '#default_value' => 604800, + ]; + + $form['max_comments'] = [ + '#type' => $this->moduleHandler->moduleExists('comment') ? 'number' : 'value', + '#title' => $this->t('Maximum number of comments per node.'), + '#description' => $this->t('You must also enable comments for the content types you are generating. Note that some nodes will randomly receive zero comments. Some will receive the max.'), + '#default_value' => $this->getSetting('max_comments'), + '#min' => 0, + '#access' => $this->moduleHandler->moduleExists('comment'), + ]; + $form['title_length'] = [ + '#type' => 'number', + '#title' => $this->t('Maximum number of words in titles'), + '#default_value' => $this->getSetting('title_length'), + '#required' => TRUE, + '#min' => 1, + '#max' => 255, + ]; + $form['add_type_label'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Prefix the title with the content type label.'), + '#description' => $this->t('This will not count against the maximum number of title words specified above.'), + '#default_value' => $this->getSetting('add_type_label'), + ]; + $form['add_alias'] = [ + '#type' => 'checkbox', + '#disabled' => !$this->moduleHandler->moduleExists('path'), + '#description' => $this->t('Requires path.module'), + '#title' => $this->t('Add an url alias for each node.'), + '#default_value' => FALSE, + ]; + $form['add_statistics'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Add statistics for each node (node_counter table).'), + '#default_value' => TRUE, + '#access' => $this->moduleHandler->moduleExists('statistics'), + ]; + + // Add the language and translation options. + $form += $this->getLanguageForm('nodes'); + + // Add the user selection checkboxes. + $author_header = [ + 'id' => $this->t('User ID'), + 'user' => $this->t('Name'), + 'role' => $this->t('Role(s)'), + ]; + + $author_rows = []; + /** @var \Drupal\user\UserInterface $user */ + foreach ($this->userStorage->loadMultiple() as $user) { + $author_rows[$user->id()] = [ + 'id' => ['#markup' => $user->id()], + 'user' => ['#markup' => $user->getAccountName()], + 'role' => ['#markup' => implode(", ", $user->getRoles())], + ]; + } + + $form['authors-wrap'] = [ + '#type' => 'details', + '#title' => $this->t('Users'), + '#open' => FALSE, + '#description' => $this->t('Select users for randomly assigning as authors of the generated content. Leave all unchecked to use a random selection of up to 50 users.'), + ]; + + $form['authors-wrap']['authors'] = [ + '#type' => 'tableselect', + '#header' => $author_header, + '#options' => $author_rows, + ]; + + $form['#redirect'] = FALSE; + + return $form; + } + + /** + * {@inheritdoc} + */ + public function settingsFormValidate(array $form, FormStateInterface $form_state) { + if (!array_filter($form_state->getValue('node_types'))) { + $form_state->setErrorByName('node_types', $this->t('Please select at least one content type')); + } + } + + /** + * {@inheritdoc} + */ + protected function generateElements(array $values) { + if ($this->isBatch($values['num'], $values['max_comments'])) { + $this->generateBatchContent($values); + } + else { + $this->generateContent($values); + } + } + + /** + * Generate content when not in batch mode. + * + * This method is used when the number of elements is under 50. + */ + private function generateContent($values) { + $values['node_types'] = array_filter($values['node_types']); + if (!empty($values['kill']) && $values['node_types']) { + $this->contentKill($values); + } + + if (!empty($values['node_types'])) { + // Generate nodes. + $this->develGenerateContentPreNode($values); + $start = time(); + $values['num_translations'] = 0; + for ($i = 1; $i <= $values['num']; $i++) { + $this->develGenerateContentAddNode($values); + if (isset($values['feedback']) && $i % $values['feedback'] == 0) { + $now = time(); + $options = [ + '@feedback' => $values['feedback'], + '@rate' => ($values['feedback'] * 60) / ($now - $start), + ]; + $this->messenger()->addStatus(dt('Completed @feedback nodes (@rate nodes/min)', $options)); + $start = $now; + } + } + } + $this->setMessage($this->formatPlural($values['num'], 'Created 1 node', 'Created @count nodes')); + if ($values['num_translations'] > 0) { + $this->setMessage($this->formatPlural($values['num_translations'], 'Created 1 node translation', 'Created @count node translations')); + } + } + + /** + * Generate content in batch mode. + * + * This method is used when the number of elements is 50 or more. + */ + private function generateBatchContent($values) { + // Remove unselected node types. + $values['node_types'] = array_filter($values['node_types']); + // If it is drushBatch then this operation is already run in the + // self::validateDrushParams(). + if (!$this->drushBatch) { + // Setup the batch operations and save the variables. + $operations[] = ['devel_generate_operation', + [$this, 'batchContentPreNode', $values], + ]; + } + + // Add the kill operation. + if ($values['kill']) { + $operations[] = ['devel_generate_operation', + [$this, 'batchContentKill', $values], + ]; + } + + // Add the operations to create the nodes. + for ($num = 0; $num < $values['num']; $num++) { + $operations[] = ['devel_generate_operation', + [$this, 'batchContentAddNode', $values], + ]; + } + + // Set the batch. + $batch = [ + 'title' => $this->t('Generating Content'), + 'operations' => $operations, + 'finished' => 'devel_generate_batch_finished', + 'file' => drupal_get_path('module', 'devel_generate') . '/devel_generate.batch.inc', + ]; + + batch_set($batch); + if ($this->drushBatch) { + drush_backend_batch_process(); + } + } + + /** + * Batch wrapper for calling ContentPreNode. + */ + public function batchContentPreNode($vars, &$context) { + $context['results'] = $vars; + $context['results']['num'] = 0; + $context['results']['num_translations'] = 0; + $this->develGenerateContentPreNode($context['results']); + } + + /** + * Batch wrapper for calling ContentAddNode. + */ + public function batchContentAddNode($vars, &$context) { + if ($this->drushBatch) { + $this->develGenerateContentAddNode($vars); + } + else { + $this->develGenerateContentAddNode($context['results']); + } + $context['results']['num']++; + if (!empty($vars['num_translations'])) { + $context['results']['num_translations'] += $vars['num_translations']; + } + } + + /** + * Batch wrapper for calling ContentKill. + */ + public function batchContentKill($vars, &$context) { + if ($this->drushBatch) { + $this->contentKill($vars); + } + else { + $this->contentKill($context['results']); + } + } + + /** + * {@inheritdoc} + */ + public function validateDrushParams(array $args, array $options = []) { + $add_language = StringUtils::csvToArray($options['languages']); + // Intersect with the enabled languages to make sure the language args + // passed are actually enabled. + $valid_languages = array_keys($this->languageManager->getLanguages(LanguageInterface::STATE_ALL)); + $values['add_language'] = array_intersect($add_language, $valid_languages); + + $translate_language = StringUtils::csvToArray($options['translations']); + $values['translate_language'] = array_intersect($translate_language, $valid_languages); + + $values['add_type_label'] = $options['add-type-label']; + $values['kill'] = $options['kill']; + $values['feedback'] = $options['feedback']; + $values['title_length'] = 6; + $values['num'] = array_shift($args); + $values['max_comments'] = array_shift($args); + $all_types = array_keys(node_type_get_names()); + $default_types = array_intersect(['page', 'article'], $all_types); + $selected_types = StringUtils::csvToArray($options['bundles'] ?: $default_types); + + if (empty($selected_types)) { + throw new \Exception(dt('No content types available')); + } + + $values['node_types'] = array_combine($selected_types, $selected_types); + $node_types = array_filter($values['node_types']); + + if (!empty($values['kill']) && empty($node_types)) { + throw new \Exception(dt('To delete content, please provide the content types (--bundles)')); + } + + // Checks for any missing content types before generating nodes. + if (array_diff($node_types, $all_types)) { + throw new \Exception(dt('One or more content types have been entered that don\'t exist on this site')); + } + + $values['authors'] = is_null($options['authors']) ? [] : explode(',', + $options['authors']); + + if ($this->isBatch($values['num'], $values['max_comments'])) { + $this->drushBatch = TRUE; + $this->develGenerateContentPreNode($values); + } + + return $values; + } + + /** + * Determines if the content should be generated in batch mode. + */ + protected function isBatch($content_count, $comment_count) { + return $content_count >= 50 || $comment_count >= 10; + } + + /** + * Deletes all nodes of given node types. + * + * @param array $values + * The input values from the settings form. + */ + protected function contentKill(array $values) { + $nids = $this->nodeStorage->getQuery() + ->condition('type', $values['node_types'], 'IN') + ->execute(); + + if (!empty($nids)) { + $nodes = $this->nodeStorage->loadMultiple($nids); + $this->nodeStorage->delete($nodes); + $this->setMessage($this->t('Deleted %count nodes.', ['%count' => count($nids)])); + } + } + + /** + * Preprocesses $results before adding content. + * + * @param array $results + * Results information. + */ + protected function develGenerateContentPreNode(array &$results) { + $authors = $results['authors']; + // Remove non-selected users. !== 0 will leave the Anonymous user in if it + // was selected on the form or entered in the drush parameters. + $authors = array_filter($authors, function ($k) { + return $k !== 0; + }); + // If no users are specified then get a random set up to a maximum of 50. + // There is no direct way randomise the selection using entity queries, so + // we use a database query instead. + if (empty($authors)) { + $query = $this->database->select('users', 'u') + ->fields('u', ['uid']) + ->range(0, 50) + ->orderRandom(); + $authors = $query->execute()->fetchCol(); + } + $results['users'] = $authors; + } + + /** + * Create one node. Used by both batch and non-batch code branches. + * + * @param array $results + * Results information. + */ + protected function develGenerateContentAddNode(array &$results) { + if (!isset($results['time_range'])) { + $results['time_range'] = 0; + } + $users = $results['users']; + + $node_type = array_rand($results['node_types']); + $uid = $users[array_rand($users)]; + + // Add the content type label if required. + $title_prefix = $results['add_type_label'] ? $this->nodeTypeStorage->load($node_type)->label() . ' - ' : ''; + + $values = [ + 'nid' => NULL, + 'type' => $node_type, + 'title' => $title_prefix . $this->getRandom()->sentences(mt_rand(1, $results['title_length']), TRUE), + 'uid' => $uid, + 'revision' => mt_rand(0, 1), + 'status' => TRUE, + 'promote' => mt_rand(0, 1), + 'created' => $this->time->getRequestTime() - mt_rand(0, $results['time_range']), + ]; + + if (isset($results['add_language'])) { + $values['langcode'] = $this->getLangcode($results['add_language']); + } + + $node = $this->nodeStorage->create($values); + + // A flag to let hook_node_insert() implementations know that this is a + // generated node. + $node->devel_generate = $results; + + // Populate all fields with sample values. + $this->populateFields($node); + + // See devel_generate_entity_insert() for actions that happen before and + // after this save. + $node->save(); + + // Add url alias if required. + if (!empty($results['add_alias'])) { + $path_alias = $this->aliasStorage->create([ + 'path' => '/node/' . $node->id(), + 'alias' => '/node-' . $node->id() . '-' . $node->bundle(), + 'langcode' => $values['langcode'] ?? LanguageInterface::LANGCODE_NOT_SPECIFIED, + ]); + $path_alias->save(); + } + + // Add translations. + if (isset($results['translate_language']) && !empty($results['translate_language'])) { + $this->develGenerateContentAddNodeTranslation($results, $node); + } + } + + /** + * Create translation for the given node. + * + * @param array $results + * Results array. + * @param \Drupal\node\NodeInterface $node + * Node to add translations to. + * + * @throws \Drupal\Core\Entity\EntityStorageException + */ + protected function develGenerateContentAddNodeTranslation(array &$results, NodeInterface $node) { + if (is_null($this->contentTranslationManager)) { + return; + } + if (!$this->contentTranslationManager->isEnabled('node', $node->getType())) { + return; + } + if ($node->langcode == LanguageInterface::LANGCODE_NOT_SPECIFIED || $node->langcode == LanguageInterface::LANGCODE_NOT_APPLICABLE) { + return; + } + + if (!isset($results['num_translations'])) { + $results['num_translations'] = 0; + } + // Translate node to each target language. + $skip_languages = [ + LanguageInterface::LANGCODE_NOT_SPECIFIED, + LanguageInterface::LANGCODE_NOT_APPLICABLE, + $node->langcode->value, + ]; + foreach ($results['translate_language'] as $langcode) { + if (in_array($langcode, $skip_languages)) { + continue; + } + $translation_node = $node->addTranslation($langcode); + $translation_node->devel_generate = $results; + $translation_node->setTitle($node->getTitle() . ' (' . $langcode . ')'); + $this->populateFields($translation_node); + $translation_node->save(); + if ($translation_node->id() > 0 && !empty($results['add_alias'])) { + $path_alias = $this->aliasStorage->create([ + 'path' => '/node/' . $translation_node->id(), + 'alias' => '/node-' . $translation_node->id() . '-' . $translation_node->bundle() . '-' . $langcode, + 'langcode' => $langcode, + ]); + $path_alias->save(); + } + $results['num_translations']++; + } + } + +} diff --git a/web/modules/contrib/devel/devel_generate/src/Plugin/DevelGenerate/MediaDevelGenerate.php b/web/modules/contrib/devel/devel_generate/src/Plugin/DevelGenerate/MediaDevelGenerate.php new file mode 100644 index 000000000..f4801daae --- /dev/null +++ b/web/modules/contrib/devel/devel_generate/src/Plugin/DevelGenerate/MediaDevelGenerate.php @@ -0,0 +1,547 @@ +mediaStorage = $entity_type_manager->getStorage('media'); + $this->mediaTypeStorage = $entity_type_manager->getStorage('media_type'); + $this->userStorage = $entity_type_manager->getStorage('user');; + $this->languageManager = $language_manager; + $this->urlGenerator = $url_generator; + $this->dateFormatter = $date_formatter; + $this->time = $time; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + return new static( + $configuration, $plugin_id, $plugin_definition, + $container->get('entity_type.manager'), + $container->get('language_manager'), + $container->get('url_generator'), + $container->get('date.formatter'), + $container->get('datetime.time') + ); + } + + /** + * {@inheritdoc} + */ + public function settingsForm(array $form, FormStateInterface $form_state) { + $types = $this->mediaTypeStorage->loadMultiple(); + + if (empty($types)) { + $create_url = $this->urlGenerator->generateFromRoute('entity.media_type.add_form'); + $this->setMessage($this->t('You do not have any media types that can be generated. Go create a new media type', [ + ':url' => $create_url, + ]), MessengerInterface::TYPE_ERROR); + return []; + } + + $options = []; + foreach ($types as $type) { + $options[$type->id()] = ['type' => ['#markup' => $type->label()]]; + } + + $form['media_types'] = [ + '#type' => 'tableselect', + '#header' => ['type' => $this->t('Media type')], + '#options' => $options, + ]; + + $form['kill'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Delete all media in these types before generating new media.'), + '#default_value' => $this->getSetting('kill'), + ]; + $form['num'] = [ + '#type' => 'number', + '#title' => $this->t('How many media items would you like to generate?'), + '#default_value' => $this->getSetting('num'), + '#required' => TRUE, + '#min' => 0, + ]; + + $options = [1 => $this->t('Now')]; + foreach ([3600, 86400, 604800, 2592000, 31536000] as $interval) { + $options[$interval] = $this->dateFormatter->formatInterval($interval, 1) . ' ' . $this->t('ago'); + } + $form['time_range'] = [ + '#type' => 'select', + '#title' => $this->t('How far back in time should the media be dated?'), + '#description' => $this->t('Media creation dates will be distributed randomly from the current time, back to the selected time.'), + '#options' => $options, + '#default_value' => 604800, + ]; + + $form['name_length'] = [ + '#type' => 'number', + '#title' => $this->t('Maximum number of words in names'), + '#default_value' => $this->getSetting('name_length'), + '#required' => TRUE, + '#min' => 1, + '#max' => 255, + ]; + + $options = []; + // We always need a language. + $languages = $this->languageManager->getLanguages(LanguageInterface::STATE_ALL); + foreach ($languages as $langcode => $language) { + $options[$langcode] = $language->getName(); + } + + $form['add_language'] = [ + '#type' => 'select', + '#title' => $this->t('Set language on media'), + '#multiple' => TRUE, + '#description' => $this->t('Requires locale.module'), + '#options' => $options, + '#default_value' => [ + $this->languageManager->getDefaultLanguage()->getId(), + ], + ]; + + $form['#redirect'] = FALSE; + + return $form; + } + + /** + * {@inheritdoc} + */ + public function settingsFormValidate(array $form, FormStateInterface $form_state) { + // Remove the media types not selected. + $media_types = array_filter($form_state->getValue('media_types')); + if (!$media_types) { + $form_state->setErrorByName('media_types', $this->t('Please select at least one media type')); + } + // Store the normalized value back, in form state. + $form_state->setValue('media_types', array_combine($media_types, $media_types)); + } + + /** + * {@inheritdoc} + */ + protected function generateElements(array $values) { + if ($this->isBatch($values['num'])) { + $this->generateBatchMedia($values); + } + else { + $this->generateMedia($values); + } + } + + /** + * Method for creating media when number of elements is less than 50. + * + * @param array $values + * Array of values submitted through a form. + * + * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException + * Thrown if the storage handler couldn't be loaded. + * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException + * Thrown if the entity type doesn't exist. + * @throws \Drupal\Core\Entity\EntityStorageException + * Thrown if the bundle does not exist or was needed but not specified. + */ + protected function generateMedia(array $values) { + if (!empty($values['kill']) && $values['media_types']) { + $this->mediaKill($values); + } + + if (!empty($values['media_types'])) { + // Generate media items. + $this->preGenerate($values); + $start = time(); + for ($i = 1; $i <= $values['num']; $i++) { + $this->createMediaItem($values); + if (isset($values['feedback']) && $i % $values['feedback'] == 0) { + $now = time(); + $this->messenger()->addStatus(dt('Completed !feedback media items (!rate media/min)', [ + '!feedback' => $values['feedback'], + '!rate' => ($values['feedback'] * 60) / ($now - $start), + ])); + $start = $now; + } + } + } + $this->setMessage($this->formatPlural($values['num'], '1 media item created.', 'Finished creating @count media items.')); + } + + /** + * Method for creating media when number of elements is greater than 50. + * + * @param array $values + * The input values from the settings form. + */ + protected function generateBatchMedia(array $values) { + $operations = []; + + // Setup the batch operations and save the variables. + $operations[] = [ + 'devel_generate_operation', + [$this, 'batchPreGenerate', $values], + ]; + + // Add the kill operation. + if ($values['kill']) { + $operations[] = [ + 'devel_generate_operation', + [$this, 'batchMediaKill', $values], + ]; + } + + // Add the operations to create the media. + for ($num = 0; $num < $values['num']; $num++) { + $operations[] = [ + 'devel_generate_operation', + [$this, 'batchCreateMediaItem', $values], + ]; + } + + // Start the batch. + $batch = [ + 'title' => $this->t('Generating media items'), + 'operations' => $operations, + 'finished' => 'devel_generate_batch_finished', + 'file' => drupal_get_path('module', 'devel_generate') . '/devel_generate.batch.inc', + ]; + batch_set($batch); + + if ($this->drushBatch) { + drush_backend_batch_process(); + } + } + + /** + * Provides a batch version of preGenerate(). + * + * @param array $vars + * The input values from the settings form. + * @param iterable $context + * Batch job context. + * + * @see self::preGenerate() + */ + public function batchPreGenerate(array $vars, iterable &$context) { + $context['results'] = $vars; + $context['results']['num'] = 0; + $this->preGenerate($context['results']); + } + + /** + * Provides a batch version of createMediaItem(). + * + * @param array $vars + * The input values from the settings form. + * @param array $context + * Batch job context. + * + * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException + * Thrown if the storage handler couldn't be loaded. + * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException + * Thrown if the entity type doesn't exist. + * @throws \Drupal\Core\Entity\EntityStorageException + * Thrown if the bundle does not exist or was needed but not specified. + * + * @see self::createMediaItem() + */ + public function batchCreateMediaItem(array $vars, &$context) { + if ($this->drushBatch) { + $this->createMediaItem($vars); + } + else { + $this->createMediaItem($context['results']); + } + $context['results']['num']++; + } + + /** + * Provides a batch version of mediaKill(). + * + * @param array $vars + * The input values from the settings form. + * @param array $context + * Batch job context. + * + * @see self::mediaKill() + */ + public function batchMediaKill($vars, &$context) { + if ($this->drushBatch) { + $this->mediaKill($vars); + } + else { + $this->mediaKill($context['results']); + } + } + + /** + * {@inheritdoc} + */ + public function validateDrushParams(array $args, array $options = []) { + $add_language = $options['languages']; + if (!empty($add_language)) { + $add_language = explode(',', str_replace(' ', '', $add_language)); + // Intersect with the enabled languages to make sure the language args + // passed are actually enabled. + $values['values']['add_language'] = array_intersect($add_language, array_keys($this->languageManager->getLanguages(LanguageInterface::STATE_ALL))); + } + + $values['kill'] = $options['kill']; + $values['feedback'] = $options['feedback']; + $values['name_length'] = 6; + $values['num'] = array_shift($args); + + $all_media_types = array_values($this->mediaTypeStorage->getQuery()->execute()); + $requested_media_types = StringUtils::csvToArray($options['media-types'] ?: $all_media_types); + + if (empty($requested_media_types)) { + throw new \Exception(dt('No media types available')); + } + // Check for any missing media type. + if ($invalid_media_types = array_diff($requested_media_types, $all_media_types)) { + throw new \Exception("Requested media types don't exists: " . implode(', ', $invalid_media_types)); + } + + $values['media_types'] = array_combine($requested_media_types, $requested_media_types); + + if ($this->isBatch($values['num'])) { + $this->drushBatch = TRUE; + $this->preGenerate($values); + } + + return $values; + } + + /** + * Deletes all media of given media media types. + * + * @param array $values + * The input values from the settings form. + * + * @throws \Drupal\Core\Entity\EntityStorageException + * Thrown if the media type does not exist. + */ + protected function mediaKill(array $values) { + $mids = $this->mediaStorage->getQuery() + ->condition('bundle', $values['media_types'], 'IN') + ->execute(); + + if (!empty($mids)) { + $media = $this->mediaStorage->loadMultiple($mids); + $this->mediaStorage->delete($media); + $this->setMessage($this->t('Deleted %count media items.', ['%count' => count($mids)])); + } + } + + /** + * Code to be run before generating items. + * + * Returns the same array passed in as parameter, but with an array of uids + * for the key 'users'. + * + * @param array $results + * The input values from the settings form. + */ + protected function preGenerate(array &$results) { + // Get user id. + $users = array_values($this->userStorage->getQuery() + ->range(0, 50) + ->execute()); + $users = array_merge($users, ['0']); + $results['users'] = $users; + } + + /** + * Create one media item. Used by both batch and non-batch code branches. + * + * @param array $results + * The input values from the settings form. + * + * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException + * Thrown if the storage handler couldn't be loaded. + * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException + * Thrown if the entity type doesn't exist. + * @throws \Drupal\Core\Entity\EntityStorageException + * Thrown if the bundle does not exist or was needed but not specified. + */ + protected function createMediaItem(array &$results) { + if (!isset($results['time_range'])) { + $results['time_range'] = 0; + } + + $media_type = array_rand($results['media_types']); + $uid = $results['users'][array_rand($results['users'])]; + + $media = $this->mediaStorage->create([ + 'bundle' => $media_type, + 'name' => $this->getRandom()->sentences(mt_rand(1, $results['name_length']), TRUE), + 'uid' => $uid, + 'revision' => mt_rand(0, 1), + 'status' => TRUE, + 'created' => $this->time->getRequestTime() - mt_rand(0, $results['time_range']), + 'langcode' => $this->getLangcode($results), + ]); + + // A flag to let hook implementations know that this is a generated item. + $media->devel_generate = $results; + + // Populate all fields with sample values. + $this->populateFields($media); + + $media->save(); + } + + /** + * Determine language based on $results. + * + * @param array $results + * The input values from the settings form. + * + * @return string + * The language code. + */ + protected function getLangcode(array $results) { + if (isset($results['add_language'])) { + $langcodes = $results['add_language']; + $langcode = $langcodes[array_rand($langcodes)]; + } + else { + $langcode = $this->languageManager->getDefaultLanguage()->getId(); + } + return $langcode; + } + + /** + * Finds out if the media item generation will run in batch process. + * + * @param int $media_items_count + * Number of media items to be generated. + * + * @return bool + * If the process should be a batch process. + */ + protected function isBatch($media_items_count) { + return $media_items_count >= 50; + } + +} diff --git a/web/modules/contrib/devel/devel_generate/src/Plugin/DevelGenerate/MenuDevelGenerate.php b/web/modules/contrib/devel/devel_generate/src/Plugin/DevelGenerate/MenuDevelGenerate.php new file mode 100644 index 000000000..79a173a4a --- /dev/null +++ b/web/modules/contrib/devel/devel_generate/src/Plugin/DevelGenerate/MenuDevelGenerate.php @@ -0,0 +1,426 @@ +menuLinkTree = $menu_tree; + $this->menuStorage = $menu_storage; + $this->menuLinkContentStorage = $menu_link_storage; + $this->moduleHandler = $module_handler; + $this->database = $database; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + $entity_type_manager = $container->get('entity_type.manager'); + return new static( + $configuration, $plugin_id, $plugin_definition, + $container->get('menu.link_tree'), + $entity_type_manager->getStorage('menu'), + $entity_type_manager->getStorage('menu_link_content'), + $container->get('module_handler'), + $container->get('database') + ); + } + + /** + * {@inheritdoc} + */ + public function settingsForm(array $form, FormStateInterface $form_state) { + $menu_enabled = $this->moduleHandler->moduleExists('menu_ui'); + if ($menu_enabled) { + $menus = ['__new-menu__' => $this->t('Create new menu(s)')] + menu_ui_get_menus(); + } + else { + $menus = menu_list_system_menus(); + } + $form['existing_menus'] = [ + '#type' => 'checkboxes', + '#title' => $this->t('Generate links for these menus'), + '#options' => $menus, + '#default_value' => ['__new-menu__'], + '#required' => TRUE, + ]; + if ($menu_enabled) { + $form['num_menus'] = [ + '#type' => 'number', + '#title' => $this->t('Number of new menus to create'), + '#default_value' => $this->getSetting('num_menus'), + '#min' => 0, + '#states' => [ + 'visible' => [ + ':input[name="existing_menus[__new-menu__]"]' => ['checked' => TRUE], + ], + ], + ]; + } + $form['num_links'] = [ + '#type' => 'number', + '#title' => $this->t('Number of links to generate'), + '#default_value' => $this->getSetting('num_links'), + '#required' => TRUE, + '#min' => 0, + ]; + $form['title_length'] = [ + '#type' => 'number', + '#title' => $this->t('Maximum length for menu titles and menu links'), + '#description' => $this->t('Text will be generated at random lengths up to this value. Enter a number between 2 and 128.'), + '#default_value' => $this->getSetting('title_length'), + '#required' => TRUE, + '#min' => 2, + '#max' => 128, + ]; + $form['link_types'] = [ + '#type' => 'checkboxes', + '#title' => $this->t('Types of links to generate'), + '#options' => [ + 'node' => $this->t('Nodes'), + 'front' => $this->t('Front page'), + 'external' => $this->t('External'), + ], + '#default_value' => ['node', 'front', 'external'], + '#required' => TRUE, + ]; + $form['max_depth'] = [ + '#type' => 'select', + '#title' => $this->t('Maximum link depth'), + '#options' => range(0, $this->menuLinkTree->maxDepth()), + '#default_value' => floor($this->menuLinkTree->maxDepth() / 2), + '#required' => TRUE, + ]; + unset($form['max_depth']['#options'][0]); + $form['max_width'] = [ + '#type' => 'number', + '#title' => $this->t('Maximum menu width'), + '#default_value' => $this->getSetting('max_width'), + '#description' => $this->t("Limit the width of the generated menu's first level of links to a certain number of items."), + '#required' => TRUE, + '#min' => 0, + ]; + $form['kill'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Delete existing custom generated menus and menu links before generating new ones.'), + '#default_value' => $this->getSetting('kill'), + ]; + + return $form; + } + + /** + * {@inheritdoc} + */ + public function generateElements(array $values) { + // If the create new menus checkbox is off, set the number of menus to 0. + if (!isset($values['existing_menus']['__new-menu__']) || !$values['existing_menus']['__new-menu__']) { + $values['num_menus'] = 0; + } + else { + // Unset the aux menu to avoid attach menu new items. + unset($values['existing_menus']['__new-menu__']); + } + + // Delete custom menus. + if ($values['kill']) { + list($menus_deleted, $links_deleted) = $this->deleteMenus(); + $this->setMessage($this->t('Deleted @menus_deleted menu(s) and @links_deleted other link(s).', + ['@menus_deleted' => $menus_deleted, '@links_deleted' => $links_deleted])); + } + + // Generate new menus. + $new_menus = $this->generateMenus($values['num_menus'], $values['title_length']); + if (!empty($new_menus)) { + $this->setMessage($this->formatPlural(count($new_menus), 'Created the following 1 new menu: @menus', 'Created the following @count new menus: @menus', + ['@menus' => implode(', ', $new_menus)])); + } + + // Generate new menu links. + $menus = $new_menus; + if (isset($values['existing_menus'])) { + $menus = $menus + $values['existing_menus']; + } + $new_links = $this->generateLinks($values['num_links'], $menus, $values['title_length'], $values['link_types'], $values['max_depth'], $values['max_width']); + $this->setMessage($this->formatPlural(count($new_links), 'Created 1 new menu link.', 'Created @count new menu links.')); + } + + /** + * {@inheritdoc} + */ + public function validateDrushParams(array $args, array $options = []) { + + $link_types = ['node', 'front', 'external']; + $values = [ + 'num_menus' => array_shift($args), + 'num_links' => array_shift($args), + 'kill' => $options['kill'], + 'pipe' => $options['pipe'], + 'link_types' => array_combine($link_types, $link_types), + ]; + + $max_depth = array_shift($args); + $max_width = array_shift($args); + $values['max_depth'] = $max_depth ? $max_depth : 3; + $values['max_width'] = $max_width ? $max_width : 8; + $values['title_length'] = $this->getSetting('title_length'); + $values['existing_menus']['__new-menu__'] = TRUE; + + if ($this->isNumber($values['num_menus']) == FALSE) { + throw new \Exception(dt('Invalid number of menus')); + } + if ($this->isNumber($values['num_links']) == FALSE) { + throw new \Exception(dt('Invalid number of links')); + } + if ($this->isNumber($values['max_depth']) == FALSE || $values['max_depth'] > 9 || $values['max_depth'] < 1) { + throw new \Exception(dt('Invalid maximum link depth. Use a value between 1 and 9')); + } + if ($this->isNumber($values['max_width']) == FALSE || $values['max_width'] < 1) { + throw new \Exception(dt('Invalid maximum menu width. Use a positive numeric value.')); + } + + return $values; + } + + /** + * Deletes custom generated menus. + */ + protected function deleteMenus() { + if ($this->moduleHandler->moduleExists('menu_ui')) { + $menu_ids = []; + foreach (menu_ui_get_menus(FALSE) as $menu => $menu_title) { + if (strpos($menu, 'devel-') === 0) { + $menu_ids[] = $menu; + } + } + + if ($menu_ids) { + $menus = $this->menuStorage->loadMultiple($menu_ids); + $this->menuStorage->delete($menus); + } + } + + // Delete menu links in other menus, but generated by devel. + $link_ids = $this->menuLinkContentStorage->getQuery() + ->condition('menu_name', 'devel', '<>') + ->condition('link__options', '%' . $this->database->escapeLike('s:5:"devel";b:1') . '%', 'LIKE') + ->execute(); + + if ($link_ids) { + $links = $this->menuLinkContentStorage->loadMultiple($link_ids); + $this->menuLinkContentStorage->delete($links); + } + + return [count($menu_ids), count($link_ids)]; + + } + + /** + * Generates new menus. + * + * @param int $num_menus + * Number of menus to create. + * @param int $title_length + * (optional) Maximum length of menu name. + * + * @return array + * Array containing the generated menus. + */ + protected function generateMenus($num_menus, $title_length = 12) { + $menus = []; + + for ($i = 1; $i <= $num_menus; $i++) { + $name = $this->randomSentenceOfLength(mt_rand(2, $title_length)); + // Create a random string of random length for the menu id. The maximum + // machine-name length is 32, so allowing for prefix 'devel-' we can have + // up to 26 here. For safety avoid accidentally reusing the same id. + do { + $id = 'devel-' . $this->getRandom()->name(mt_rand(2, 26)); + } while (array_key_exists($id, $menus)); + + $menu = $this->menuStorage->create([ + 'label' => $name, + 'id' => $id, + 'description' => $this->t('Description of @name', ['@name' => $name]), + ]); + + $menu->save(); + $menus[$menu->id()] = $menu->label(); + } + + return $menus; + } + + /** + * Generates menu links in a tree structure. + */ + protected function generateLinks($num_links, $menus, $title_length, $link_types, $max_depth, $max_width) { + $links = []; + $menus = array_keys(array_filter($menus)); + $link_types = array_keys(array_filter($link_types)); + + $nids = []; + for ($i = 1; $i <= $num_links; $i++) { + // Pick a random menu. + $menu_name = $menus[array_rand($menus)]; + // Build up our link. + $link_title = $this->getRandom()->word(mt_rand(2, max(2, $title_length))); + $link = $this->menuLinkContentStorage->create([ + 'menu_name' => $menu_name, + 'weight' => mt_rand(-50, 50), + 'title' => $link_title, + 'bundle' => 'menu_link_content', + 'description' => $this->t('Description of @title.', ['@title' => $link_title]), + ]); + $link->link->options = ['devel' => TRUE]; + + // For the first $max_width items, make first level links. + if ($i <= $max_width) { + $depth = 0; + } + else { + // Otherwise, get a random parent menu depth. + $depth = mt_rand(1, max(1, $max_depth - 1)); + } + // Get a random parent link from the proper depth. + do { + $parameters = new MenuTreeParameters(); + $parameters->setMinDepth($depth); + $parameters->setMaxDepth($depth); + $tree = $this->menuLinkTree->load($menu_name, $parameters); + + if ($tree) { + $link->parent = array_rand($tree); + } + $depth--; + } while (!$link->parent && $depth > 0); + + $link_type = array_rand($link_types); + switch ($link_types[$link_type]) { + case 'node': + // Grab a random node ID. + $select = $this->database->select('node_field_data', 'n') + ->fields('n', ['nid', 'title']) + ->condition('n.status', 1) + ->range(0, 1) + ->orderRandom(); + // Don't put a node into the menu twice. + if (!empty($nids[$menu_name])) { + $select->condition('n.nid', $nids[$menu_name], 'NOT IN'); + } + $node = $select->execute()->fetchAssoc(); + if (isset($node['nid'])) { + $nids[$menu_name][] = $node['nid']; + $link->link->uri = 'entity:node/' . $node['nid']; + $link->title = $node['title']; + break; + } + + case 'external': + $link->link->uri = 'http://www.example.com/'; + break; + + case 'front': + $link->link->uri = 'internal:/'; + break; + + default: + $link->devel_link_type = $link_type; + break; + } + + $link->save(); + + $links[$link->id()] = $link->link_title; + } + + return $links; + } + +} diff --git a/web/modules/contrib/devel/devel_generate/src/Plugin/DevelGenerate/TermDevelGenerate.php b/web/modules/contrib/devel/devel_generate/src/Plugin/DevelGenerate/TermDevelGenerate.php new file mode 100644 index 000000000..44b300e64 --- /dev/null +++ b/web/modules/contrib/devel/devel_generate/src/Plugin/DevelGenerate/TermDevelGenerate.php @@ -0,0 +1,477 @@ +vocabularyStorage = $vocabulary_storage; + $this->termStorage = $term_storage; + $this->database = $database; + $this->moduleHandler = $module_handler; + $this->languageManager = $language_manager; + $this->contentTranslationManager = $content_translation_manager; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + $entity_type_manager = $container->get('entity_type.manager'); + return new static( + $configuration, $plugin_id, $plugin_definition, + $entity_type_manager->getStorage('taxonomy_vocabulary'), + $entity_type_manager->getStorage('taxonomy_term'), + $container->get('database'), + $container->get('module_handler'), + $container->get('language_manager'), + $container->has('content_translation.manager') ? $container->get('content_translation.manager') : NULL + ); + } + + /** + * {@inheritdoc} + */ + public function settingsForm(array $form, FormStateInterface $form_state) { + $options = []; + foreach ($this->vocabularyStorage->loadMultiple() as $vocabulary) { + $options[$vocabulary->id()] = $vocabulary->label(); + } + // Sort by vocabulary label. + asort($options); + // Set default to 'tags' only if it exists as a vocabulary. + $default_vids = array_key_exists('tags', $options) ? 'tags' : ''; + $form['vids'] = [ + '#type' => 'select', + '#multiple' => TRUE, + '#title' => $this->t('Vocabularies'), + '#required' => TRUE, + '#default_value' => $default_vids, + '#options' => $options, + '#description' => $this->t('Restrict terms to these vocabularies.'), + ]; + $form['num'] = [ + '#type' => 'number', + '#title' => $this->t('Number of terms'), + '#default_value' => $this->getSetting('num'), + '#required' => TRUE, + '#min' => 0, + ]; + $form['title_length'] = [ + '#type' => 'number', + '#title' => $this->t('Maximum number of characters in term names'), + '#default_value' => $this->getSetting('title_length'), + '#required' => TRUE, + '#min' => 2, + '#max' => 255, + ]; + $form['minimum_depth'] = [ + '#type' => 'number', + '#title' => $this->t('Minimum depth for new terms in the vocabulary hierarchy'), + '#description' => $this->t('Enter a value from 1 to 20.'), + '#default_value' => $this->getSetting('minimum_depth'), + '#min' => 1, + '#max' => 20, + ]; + $form['maximum_depth'] = [ + '#type' => 'number', + '#title' => $this->t('Maximum depth for new terms in the vocabulary hierarchy'), + '#description' => $this->t('Enter a value from 1 to 20.'), + '#default_value' => $this->getSetting('maximum_depth'), + '#min' => 1, + '#max' => 20, + ]; + $form['kill'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Delete existing terms in specified vocabularies before generating new terms.'), + '#default_value' => $this->getSetting('kill'), + ]; + + // Add the language and translation options. + $form += $this->getLanguageForm('terms'); + + return $form; + } + + /** + * {@inheritdoc} + */ + public function generateElements(array $values) { + $new_terms = $this->generateTerms($values); + if (!empty($new_terms['terms'])) { + $this->setMessage($this->formatPlural($new_terms['terms'], 'Created 1 new term', 'Created @count new terms')); + + // Helper function to format the number of terms and the list of terms. + $format_terms_func = function ($data, $level) { + if ($data['total'] > 10) { + $data['terms'][] = '...'; + } + return $this->formatPlural($data['total'], + '1 new term at level @level (@terms)', + '@count new terms at level @level (@terms)', + ['@level' => $level, '@terms' => implode(',', $data['terms'])]); + }; + + foreach ($new_terms['vocabs'] as $vid => $vlabel) { + if (array_key_exists($vid, $new_terms)) { + ksort($new_terms[$vid]); + $termlist = implode(', ', array_map($format_terms_func, $new_terms[$vid], array_keys($new_terms[$vid]))); + $this->setMessage($this->t('In vocabulary @vlabel: @termlist', ['@vlabel' => $vlabel, '@termlist' => $termlist])); + } + else { + $this->setMessage($this->t('In vocabulary @vlabel: No terms created', ['@vlabel' => $vlabel])); + } + } + + } + if ($new_terms['terms_translations'] > 0) { + $this->setMessage($this->formatPlural($new_terms['terms_translations'], 'Created 1 term translation', 'Created @count term translations')); + } + } + + /** + * Deletes all terms of given vocabularies. + * + * @param array $vids + * Array of vocabulary ids. + * + * @return int + * The number of terms deleted. + */ + protected function deleteVocabularyTerms(array $vids) { + $tids = $this->vocabularyStorage->getToplevelTids($vids); + $terms = $this->termStorage->loadMultiple($tids); + $total_deleted = 0; + foreach ($vids as $vid) { + $total_deleted += count($this->termStorage->loadTree($vid)); + } + $this->termStorage->delete($terms); + return $total_deleted; + } + + /** + * Generates taxonomy terms for a list of given vocabularies. + * + * @param array $parameters + * The input parameters from the settings form or drush command. + * + * @return array + * Information about the created terms. + */ + protected function generateTerms(array $parameters) { + $info = [ + 'terms' => 0, + 'terms_translations' => 0, + ]; + $min_depth = $parameters['minimum_depth']; + $max_depth = $parameters['maximum_depth']; + + // $parameters['vids'] from the UI has keys of the vocab ids. From drush + // the array is keyed 0,1,2. Therefore create $vocabs which has keys of the + // vocab ids, so it can be used with array_rand(). + $vocabs = array_combine($parameters['vids'], $parameters['vids']); + + // Build an array of potential parents for the new terms. These will be + // terms in the vocabularies we are creating in, which have a depth of one + // less than the minimum for new terms up to one less than the maximum. + $all_parents = []; + foreach ($parameters['vids'] as $vid) { + $info['vocabs'][$vid] = $this->vocabularyStorage->load($vid)->label(); + // Initialise the nested array for this vocabulary. + $all_parents[$vid] = ['top_level' => [], 'lower_levels' => []]; + for ($depth = 1; $depth < $max_depth; $depth++) { + $query = \Drupal::entityQuery('taxonomy_term')->condition('vid', $vid); + if ($depth == 1) { + // For the top level the parent id must be zero. + $query->condition('parent', 0); + } + else { + // For lower levels use the $ids array obtained in the previous loop. + // phpcs:ignore DrupalPractice.CodeAnalysis.VariableAnalysis.UndefinedVariable + $query->condition('parent', $ids, 'IN'); + } + $ids = $query->execute(); + + if (empty($ids)) { + // Reached the end, no more parents to be found. + break; + } + + // Store these terms as parents if they are within the depth range for + // new terms. + if ($depth == $min_depth - 1) { + $all_parents[$vid]['top_level'] = array_fill_keys($ids, $depth); + } + elseif ($depth >= $min_depth) { + $all_parents[$vid]['lower_levels'] += array_fill_keys($ids, $depth); + } + } + // No top-level parents will have been found above when the minimum depth + // is 1 so add a record for that data here. + if ($min_depth == 1) { + $all_parents[$vid]['top_level'] = [0 => 0]; + } + elseif (empty($all_parents[$vid]['top_level'])) { + // No parents for required minimum level so cannot use this vocabulary. + unset($vocabs[$vid]); + } + } + + if (empty($vocabs)) { + // There are no available parents at the required depth in any vocabulary + // so we cannot create any new terms. + throw new \Exception(sprintf('Invalid minimum depth %s because there are no terms in any vocabulary at depth %s', $min_depth, $min_depth - 1)); + } + + // Only delete terms from the vocabularies we can create new terms in. + if ($parameters['kill']) { + $deleted = $this->deleteVocabularyTerms($vocabs); + $this->setMessage($this->formatPlural($deleted, 'Deleted 1 existing term', 'Deleted @count existing terms')); + } + + // Insert new data: + for ($i = 1; $i <= $parameters['num']; $i++) { + // Select a vocabulary at random. + $vid = array_rand($vocabs); + + // Set the group to use to select a random parent from. Using < 50 means + // on average half of the new terms will be top_level. Also if no terms + // exist yet in 'lower_levels' then we have to use 'top_level'. + $group = (mt_rand(0, 100) < 50 || empty($all_parents[$vid]['lower_levels'])) ? 'top_level' : 'lower_levels'; + $parent = array_rand($all_parents[$vid][$group]); + $depth = $all_parents[$vid][$group][$parent] + 1; + $name = $this->getRandom()->word(mt_rand(2, $parameters['title_length'])); + + $values = [ + 'name' => $name, + 'description' => 'Description of ' . $name . ' (depth ' . $depth . ')', + 'format' => filter_fallback_format(), + 'weight' => mt_rand(0, 10), + 'vid' => $vid, + 'parent' => [$parent], + ]; + if (isset($parameters['add_language'])) { + $values['langcode'] = $this->getLangcode($parameters['add_language']); + } + $term = $this->termStorage->create($values); + + // A flag to let hook implementations know that this is a generated term. + $term->devel_generate = TRUE; + + // Populate all fields with sample values. + $this->populateFields($term); + $term->save(); + + // Add translations. + if (isset($parameters['translate_language']) && !empty($parameters['translate_language'])) { + $info['terms_translations'] += $this->generateTermTranslation($parameters['translate_language'], $term); + } + + // If the depth of the new term is less than the maximum depth then it can + // also be saved as a potential parent for the subsequent new terms. + if ($depth < $max_depth) { + $all_parents[$vid]['lower_levels'] += [$term->id() => $depth]; + } + + // Store data about the newly generated term. + $info['terms']++; + @$info[$vid][$depth]['total']++; + // List only the first 10 new terms at each vocab/level. + if (!isset($info[$vid][$depth]['terms']) || count($info[$vid][$depth]['terms']) < 10) { + $info[$vid][$depth]['terms'][] = $term->label(); + } + + unset($term); + } + + return $info; + } + + /** + * Create translation for the given term. + * + * @param array $translate_language + * Potential translate languages array. + * @param \Drupal\taxonomy\TermInterface $term + * Term to add translations to. + * + * @return int + * Number of translations added. + */ + protected function generateTermTranslation(array $translate_language, TermInterface $term) { + if (is_null($this->contentTranslationManager)) { + return 0; + } + if (!$this->contentTranslationManager->isEnabled('taxonomy_term', $term->bundle())) { + return 0; + } + if ($term->langcode == LanguageInterface::LANGCODE_NOT_SPECIFIED || $term->langcode == LanguageInterface::LANGCODE_NOT_APPLICABLE) { + return 0; + } + + $num_translations = 0; + // Translate term to each target language. + $skip_languages = [ + LanguageInterface::LANGCODE_NOT_SPECIFIED, + LanguageInterface::LANGCODE_NOT_APPLICABLE, + $term->langcode->value, + ]; + foreach ($translate_language as $langcode) { + if (in_array($langcode, $skip_languages)) { + continue; + } + $translation_term = $term->addTranslation($langcode); + $translation_term->setName($term->getName() . ' (' . $langcode . ')'); + $this->populateFields($translation_term); + $translation_term->save(); + $num_translations++; + } + return $num_translations; + } + + /** + * {@inheritdoc} + */ + public function validateDrushParams(array $args, array $options = []) { + // Get default settings from the annotated command definition. + $defaultSettings = $this->getDefaultSettings(); + + $bundles = StringUtils::csvToarray($options['bundles']); + if (count($bundles) < 1) { + throw new \Exception(dt('Please provide a vocabulary machine name (--bundles).')); + } + foreach ($bundles as $bundle) { + // Verify that each bundle is a valid vocabulary id. + if (!$this->vocabularyStorage->load($bundle)) { + throw new \Exception(dt('Invalid vocabulary machine name: @name', ['@name' => $bundle])); + } + } + + $number = array_shift($args) ?: $defaultSettings['num']; + if (!$this->isNumber($number)) { + throw new \Exception(dt('Invalid number of terms: @num', ['@num' => $number])); + } + + $minimum_depth = $options['min-depth'] ?? $defaultSettings['minimum_depth']; + $maximum_depth = $options['max-depth'] ?? $defaultSettings['maximum_depth']; + if ($minimum_depth < 1 || $minimum_depth > 20 || $maximum_depth < 1 || $maximum_depth > 20 || $minimum_depth > $maximum_depth) { + throw new \Exception(dt('The depth values must be in the range 1 to 20 and min-depth cannot be larger than max-depth (values given: min-depth @min, max-depth @max)', ['@min' => $minimum_depth, '@max' => $maximum_depth])); + } + + $values = [ + 'num' => $number, + 'kill' => $options['kill'], + 'title_length' => 12, + 'vids' => $bundles, + 'minimum_depth' => $minimum_depth, + 'maximum_depth' => $maximum_depth, + ]; + $add_language = StringUtils::csvToArray($options['languages']); + // Intersect with the enabled languages to make sure the language args + // passed are actually enabled. + $valid_languages = array_keys($this->languageManager->getLanguages(LanguageInterface::STATE_ALL)); + $values['add_language'] = array_intersect($add_language, $valid_languages); + + $translate_language = StringUtils::csvToArray($options['translations']); + $values['translate_language'] = array_intersect($translate_language, $valid_languages); + return $values; + } + +} diff --git a/web/modules/contrib/devel/devel_generate/src/Plugin/DevelGenerate/UserDevelGenerate.php b/web/modules/contrib/devel/devel_generate/src/Plugin/DevelGenerate/UserDevelGenerate.php new file mode 100644 index 000000000..69d0b15e8 --- /dev/null +++ b/web/modules/contrib/devel/devel_generate/src/Plugin/DevelGenerate/UserDevelGenerate.php @@ -0,0 +1,206 @@ +userStorage = $entity_storage; + $this->dateFormatter = $date_formatter; + $this->time = $time; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + return new static( + $configuration, $plugin_id, $plugin_definition, + $container->get('entity_type.manager')->getStorage('user'), + $container->get('date.formatter'), + $container->get('datetime.time') + ); + } + + /** + * {@inheritdoc} + */ + public function settingsForm(array $form, FormStateInterface $form_state) { + $form['num'] = [ + '#type' => 'number', + '#title' => $this->t('How many users would you like to generate?'), + '#default_value' => $this->getSetting('num'), + '#required' => TRUE, + '#min' => 0, + ]; + + $form['kill'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Delete all users (except user id 1) before generating new users.'), + '#default_value' => $this->getSetting('kill'), + ]; + + $options = user_role_names(TRUE); + unset($options[AccountInterface::AUTHENTICATED_ROLE]); + $form['roles'] = [ + '#type' => 'checkboxes', + '#title' => $this->t('Which roles should the users receive?'), + '#description' => $this->t('Users always receive the authenticated user role.'), + '#options' => $options, + ]; + + $form['pass'] = [ + '#type' => 'textfield', + '#title' => $this->t('Password to be set'), + '#default_value' => $this->getSetting('pass'), + '#size' => 32, + '#description' => $this->t('Leave this field empty if you do not need to set a password'), + ]; + + $options = [1 => $this->t('Now')]; + foreach ([3600, 86400, 604800, 2592000, 31536000] as $interval) { + $options[$interval] = $this->dateFormatter->formatInterval($interval, 1) . ' ' . $this->t('ago'); + } + $form['time_range'] = [ + '#type' => 'select', + '#title' => $this->t('How old should user accounts be?'), + '#description' => $this->t('User ages will be distributed randomly from the current time, back to the selected time.'), + '#options' => $options, + '#default_value' => 604800, + ]; + + return $form; + } + + /** + * {@inheritdoc} + */ + protected function generateElements(array $values) { + $num = $values['num']; + $kill = $values['kill']; + $pass = $values['pass']; + $age = $values['time_range']; + $roles = array_filter($values['roles']); + + if ($kill) { + $uids = $this->userStorage->getQuery() + ->condition('uid', 1, '>') + ->execute(); + $users = $this->userStorage->loadMultiple($uids); + $this->userStorage->delete($users); + + $this->setMessage($this->formatPlural(count($uids), '1 user deleted', '@count users deleted.')); + } + + if ($num > 0) { + $names = []; + while (count($names) < $num) { + $name = $this->getRandom()->word(mt_rand(6, 12)); + $names[$name] = ''; + } + + if (empty($roles)) { + $roles = [AccountInterface::AUTHENTICATED_ROLE]; + } + foreach ($names as $name => $value) { + $account = $this->userStorage->create([ + 'uid' => NULL, + 'name' => $name, + 'pass' => $pass, + 'mail' => $name . '@example.com', + 'status' => 1, + 'created' => $this->time->getRequestTime() - mt_rand(0, $age), + 'roles' => array_values($roles), + // A flag to let hook_user_* know that this is a generated user. + 'devel_generate' => TRUE, + ]); + + // Populate all fields with sample values. + $this->populateFields($account); + $account->save(); + } + } + $this->setMessage($this->t('@num_users created.', + ['@num_users' => $this->formatPlural($num, '1 user', '@count users')])); + } + + /** + * {@inheritdoc} + */ + public function validateDrushParams(array $args, array $options = []) { + $values = [ + 'num' => array_shift($args), + 'time_range' => 0, + 'roles' => StringUtils::csvToArray($options['roles']), + 'kill' => $options['kill'], + 'pass' => $options['pass'], + ]; + return $values; + } + +} diff --git a/web/modules/contrib/devel/devel_generate/src/Plugin/DevelGenerate/VocabularyDevelGenerate.php b/web/modules/contrib/devel/devel_generate/src/Plugin/DevelGenerate/VocabularyDevelGenerate.php new file mode 100644 index 000000000..a8c1a592a --- /dev/null +++ b/web/modules/contrib/devel/devel_generate/src/Plugin/DevelGenerate/VocabularyDevelGenerate.php @@ -0,0 +1,174 @@ +vocabularyStorage = $entity_storage; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + return new static( + $configuration, $plugin_id, $plugin_definition, + $container->get('entity_type.manager')->getStorage('taxonomy_vocabulary') + ); + } + + /** + * {@inheritdoc} + */ + public function settingsForm(array $form, FormStateInterface $form_state) { + $form['num'] = [ + '#type' => 'number', + '#title' => $this->t('Number of vocabularies?'), + '#default_value' => $this->getSetting('num'), + '#required' => TRUE, + '#min' => 0, + ]; + $form['title_length'] = [ + '#type' => 'number', + '#title' => $this->t('Maximum number of characters in vocabulary names'), + '#default_value' => $this->getSetting('title_length'), + '#required' => TRUE, + '#min' => 2, + '#max' => 255, + ]; + $form['kill'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Delete existing vocabularies before generating new ones.'), + '#default_value' => $this->getSetting('kill'), + ]; + + return $form; + } + + /** + * {@inheritdoc} + */ + public function generateElements(array $values) { + if ($values['kill']) { + $this->deleteVocabularies(); + $this->setMessage($this->t('Deleted existing vocabularies.')); + } + + $new_vocs = $this->generateVocabularies($values['num'], $values['title_length']); + if (!empty($new_vocs)) { + $this->setMessage($this->t('Created the following new vocabularies: @vocs', ['@vocs' => implode(', ', $new_vocs)])); + } + } + + /** + * Deletes all vocabularies. + */ + protected function deleteVocabularies() { + $vocabularies = $this->vocabularyStorage->loadMultiple(); + $this->vocabularyStorage->delete($vocabularies); + } + + /** + * Generates vocabularies. + * + * @param int $records + * Number of vocabularies to create. + * @param int $maxlength + * (optional) Maximum length for vocabulary name. + * + * @return array + * Array containing the generated vocabularies id. + */ + protected function generateVocabularies($records, $maxlength = 12) { + $vocabularies = []; + + // Insert new data: + for ($i = 1; $i <= $records; $i++) { + $name = $this->getRandom()->word(mt_rand(2, $maxlength)); + + $vocabulary = $this->vocabularyStorage->create([ + 'name' => $name, + 'vid' => mb_strtolower($name), + 'langcode' => Language::LANGCODE_NOT_SPECIFIED, + 'description' => "Description of $name", + 'hierarchy' => 1, + 'weight' => mt_rand(0, 10), + 'multiple' => 1, + 'required' => 0, + 'relations' => 1, + ]); + + // Populate all fields with sample values. + $this->populateFields($vocabulary); + $vocabulary->save(); + + $vocabularies[] = $vocabulary->id(); + unset($vocabulary); + } + + return $vocabularies; + } + + /** + * {@inheritdoc} + */ + public function validateDrushParams(array $args, array $options = []) { + $values = [ + 'num' => array_shift($args), + 'kill' => $options['kill'], + 'title_length' => 12, + ]; + + if ($this->isNumber($values['num']) == FALSE) { + throw new \Exception(dt('Invalid number of vocabularies: @num.', ['@num' => $values['num']])); + } + + return $values; + } + +} diff --git a/web/modules/contrib/devel/devel_generate/src/Routing/DevelGenerateRoutes.php b/web/modules/contrib/devel/devel_generate/src/Routing/DevelGenerateRoutes.php new file mode 100644 index 000000000..eb302b00f --- /dev/null +++ b/web/modules/contrib/devel/devel_generate/src/Routing/DevelGenerateRoutes.php @@ -0,0 +1,73 @@ +DevelGenerateManager = $devel_generate_manager; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('plugin.manager.develgenerate') + ); + } + + /** + * Define routes for all devel_generate plugins. + */ + public function routes() { + $devel_generate_plugins = $this->DevelGenerateManager->getDefinitions(); + + $routes = []; + foreach ($devel_generate_plugins as $id => $plugin) { + $label = $plugin['label']; + $type_url_str = str_replace('_', '-', $plugin['url']); + $routes["devel_generate.$id"] = new Route( + "admin/config/development/generate/$type_url_str", + [ + '_form' => '\Drupal\devel_generate\Form\DevelGenerateForm', + '_title' => "Generate $label", + '_plugin_id' => $id, + ], + [ + '_permission' => $plugin['permission'], + ] + ); + } + + // Add the route for the 'Generate' admin group on the admin/config page. + // This also provides the page for all devel_generate links. + $routes['devel_generate.admin_config_generate'] = new Route( + '/admin/config/development/generate', + [ + '_controller' => '\Drupal\system\Controller\SystemController::systemAdminMenuBlockPage', + '_title' => 'Generate', + ], + [ + '_permission' => 'administer devel_generate', + ] + ); + + return $routes; + } + +} diff --git a/web/modules/contrib/devel/devel_generate/tests/modules/devel_generate_example/devel_generate_example.info.yml b/web/modules/contrib/devel/devel_generate/tests/modules/devel_generate_example/devel_generate_example.info.yml new file mode 100644 index 000000000..b0c5fb954 --- /dev/null +++ b/web/modules/contrib/devel/devel_generate/tests/modules/devel_generate_example/devel_generate_example.info.yml @@ -0,0 +1,13 @@ +name: 'Devel Generate Example' +type: module +description: 'Create an example of a Devel Generate plugin type for testing purposes.' +package: Development +core_version_requirement: ^8.8 || ^9 +configure: admin/config/development/generate +tags: + - developer + +# Information added by Drupal.org packaging script on 2020-12-31 +version: '4.1.1' +project: 'devel' +datestamp: 1609419530 diff --git a/web/modules/contrib/devel/devel_generate/tests/modules/devel_generate_example/src/Plugin/DevelGenerate/ExampleDevelGenerate.php b/web/modules/contrib/devel/devel_generate/tests/modules/devel_generate_example/src/Plugin/DevelGenerate/ExampleDevelGenerate.php new file mode 100644 index 000000000..eff39752f --- /dev/null +++ b/web/modules/contrib/devel/devel_generate/tests/modules/devel_generate_example/src/Plugin/DevelGenerate/ExampleDevelGenerate.php @@ -0,0 +1,97 @@ + 'textfield', + '#title' => $this->t('How many examples would you like to generate?'), + '#default_value' => $this->getSetting('num'), + '#size' => 10, + ]; + + $form['kill'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Delete all examples before generating new examples.'), + '#default_value' => $this->getSetting('kill'), + ]; + + return $form; + } + + /** + * {@inheritdoc} + */ + protected function generateElements(array $values) { + $num = $values['num']; + $kill = $values['kill']; + + if ($kill) { + $this->setMessage($this->t('Old examples have been deleted.')); + } + + // Creating user in order to demonstrate + // how to override default business login generation. + $edit = [ + 'uid' => NULL, + 'name' => 'example_devel_generate', + 'pass' => '', + 'mail' => 'example_devel_generate@example.com', + 'status' => 1, + 'created' => \Drupal::time()->getRequestTime(), + 'roles' => '', + // A flag to let hook_user_* know that this is a generated user. + 'devel_generate' => TRUE, + ]; + + $account = user_load_by_name('example_devel_generate'); + if (!$account) { + $account = $this->getEntityTypeManager()->getStorage('user')->create($edit); + } + + // Populate all fields with sample values. + $this->populateFields($account); + + $account->save(); + + $this->setMessage($this->t('@num_examples created.', [ + '@num_examples' => $this->formatPlural($num, '1 example', '@count examples'), + ])); + } + + /** + * + */ + public function validateDrushParams(array $args, array $options = []) { + $values = [ + 'num' => $options['num'], + 'kill' => $options['kill'], + ]; + return $values; + } + +} diff --git a/web/modules/contrib/devel/devel_generate/tests/src/Functional/DevelGenerateBrowserTest.php b/web/modules/contrib/devel/devel_generate/tests/src/Functional/DevelGenerateBrowserTest.php new file mode 100644 index 000000000..f54e02cb4 --- /dev/null +++ b/web/modules/contrib/devel/devel_generate/tests/src/Functional/DevelGenerateBrowserTest.php @@ -0,0 +1,412 @@ + 4, + ]; + $this->drupalPostForm('admin/config/development/generate/user', $edit, 'Generate'); + $this->assertText('4 users created.'); + $this->assertText('Generate process complete.'); + + } + + /** + * Tests generating content. + */ + public function testDevelGenerateContent() { + // Tests that if no content types are selected an error message is shown. + $edit = [ + 'num' => 4, + 'title_length' => 4, + ]; + $this->drupalPostForm('admin/config/development/generate/content', $edit, 'Generate'); + $this->assertText('Please select at least one content type'); + + // Create a node in order to test the Delete content checkbox. + $this->drupalCreateNode(['type' => 'article']); + + // Generate articles with comments and aliases. + $edit = [ + 'num' => 4, + 'kill' => TRUE, + 'node_types[article]' => TRUE, + 'time_range' => 604800, + 'max_comments' => 3, + 'title_length' => 4, + 'add_alias' => 1, + ]; + $this->drupalPostForm('admin/config/development/generate/content', $edit, 'Generate'); + $this->assertSession()->pageTextContains('Deleted 1 node'); + $this->assertSession()->pageTextContains('Created 4 nodes'); + $this->assertSession()->pageTextContains('Generate process complete.'); + $this->assertSession()->pageTextNotContains('translations'); + + // Tests that nodes have been created in the generation process. + $nodes = Node::loadMultiple(); + $this->assert(count($nodes) == 4, 'Nodes generated successfully.'); + + // Tests url alias for the generated nodes. + foreach ($nodes as $node) { + $alias = 'node-' . $node->id() . '-' . $node->bundle(); + $this->drupalGet($alias); + $this->assertSession()->statusCodeEquals('200'); + $this->assertSession()->pageTextContains($node->getTitle(), 'Generated url alias for the node works.'); + } + + // Generate articles with translations. + $edit = [ + 'num' => 3, + 'kill' => TRUE, + 'node_types[article]' => TRUE, + 'add_language[]' => ['en'], + 'translate_language[]' => ['de', 'ca'], + 'add_alias' => TRUE, + ]; + $this->drupalPostForm('admin/config/development/generate/content', $edit, 'Generate'); + $this->assertSession()->pageTextContains('Deleted 4 nodes'); + $this->assertSession()->pageTextContains('Created 3 nodes'); + // Two translations for each node makes six. + $this->assertSession()->pageTextContains('Created 6 node translations'); + $articles = \Drupal::entityQuery('node')->execute(); + $this->assertCount(3, $articles); + $node = Node::load(end($articles)); + $this->assertTrue($node->hasTranslation('de')); + $this->assertTrue($node->hasTranslation('ca')); + $this->assertFalse($node->hasTranslation('fr')); + + // Check url alias for each of the translations. + foreach (Node::loadMultiple($articles) as $node) { + foreach (['de', 'ca'] as $langcode) { + $translation_node = $node->getTranslation($langcode); + $alias = 'node-' . $translation_node->id() . '-' . $translation_node->bundle() . '-' . $langcode; + $this->drupalGet($langcode . '/' . $alias); + $this->assertSession()->statusCodeEquals('200'); + $this->assertSession()->pageTextContains($translation_node->getTitle()); + } + } + + // Create article to make sure it is not deleted when only killing pages. + $article = $this->drupalCreateNode(['type' => 'article', 'title' => 'Alive']); + // The 'page' content type is not enabled for translation. + $edit = [ + 'num' => 2, + 'kill' => TRUE, + 'node_types[page]' => TRUE, + 'add_language[]' => ['en'], + 'translate_language[]' => ['fr'], + ]; + $this->drupalPostForm('admin/config/development/generate/content', $edit, 'Generate'); + $this->assertSession()->pageTextNotContains('Deleted'); + $this->assertSession()->pageTextContains('Created 2 nodes'); + $this->assertSession()->pageTextNotContains('node translations'); + // Check that 'kill' has not deleted the article. + $this->assertNotEmpty(Node::load($article->id())); + $pages = \Drupal::entityQuery('node')->condition('type', 'page')->execute(); + $this->assertCount(2, $pages); + $node = Node::load(end($pages)); + $this->assertFalse($node->hasTranslation('fr')); + + // Create articles with add-type-label option. + $edit = [ + 'num' => 5, + 'kill' => TRUE, + 'node_types[article]' => TRUE, + 'add_type_label' => TRUE, + ]; + $this->drupalPostForm('admin/config/development/generate/content', $edit, 'Generate'); + $this->assertSession()->pageTextContains('Created 5 nodes'); + $this->assertSession()->pageTextContains('Generate process complete'); + + // Count the articles created in the generation process. + $nodes = \Drupal::entityQuery('node')->condition('type', 'article')->execute(); + $this->assertCount(5, $nodes); + + // Load the final node and verify that the title starts with the label. + $node = Node::load(end($nodes)); + $this->assertEquals('Article - ', substr($node->title->value, 0, 10)); + + // Test creating content with specified authors. First create 15 more users + // making 18 in total, to make the test much stronger. + for ($i = 0; $i < 15; $i++) { + $this->drupalCreateUser(); + } + $edit = [ + 'num' => 10, + 'kill' => TRUE, + 'node_types[article]' => TRUE, + 'authors[3]' => TRUE, + 'authors[4]' => TRUE, + ]; + $this->drupalPostForm('admin/config/development/generate/content', $edit, 'Generate'); + + // Display the full content list for information and debug only. + $this->drupalGet('admin/content'); + + // Count all the articles by user 3 and 4 and by others. We count the two + // users nodes separately to ensure that there are some by each user. + $nodes_by_user_3 = \Drupal::entityQuery('node')->condition('type', 'article')->condition('uid', ['3'], 'IN')->execute(); + $nodes_by_user_4 = \Drupal::entityQuery('node')->condition('type', 'article')->condition('uid', ['4'], 'IN')->execute(); + $nodes_by_others = \Drupal::entityQuery('node')->condition('type', 'article')->condition('uid', ['3', '4'], 'NOT IN')->execute(); + + // If the user option was not working correctly and users were assigned at + // random, then the chance that these assertions will correctly detect the + // error is 1 - (2/18 ** 10) = 99.99%. + $this->assertEquals(10, count($nodes_by_user_3) + count($nodes_by_user_4)); + $this->assertCount(0, $nodes_by_others); + + // If the user option is coded correctly the chance of either of these + // assertions giving a false failure is 1/2 ** 10 = 0.097%. + $this->assertGreaterThan(0, count($nodes_by_user_3)); + $this->assertGreaterThan(0, count($nodes_by_user_4)); + } + + /** + * Tests generating terms. + */ + public function testDevelGenerateTerms() { + // Generate terms. + $edit = [ + 'vids[]' => $this->vocabulary->id(), + 'num' => 5, + 'title_length' => 12, + ]; + $this->drupalPostForm('admin/config/development/generate/term', $edit, 'Generate'); + $this->assertSession()->pageTextContains('Created 5 new terms'); + $this->assertSession()->pageTextContains('In vocabulary ' . $this->vocabulary->label()); + $this->assertSession()->pageTextNotContains('translations'); + $this->assertSession()->pageTextContains('Generate process complete.'); + $this->assertCount(5, \Drupal::entityQuery('taxonomy_term')->execute()); + + // Generate terms with translations. + $edit = [ + 'vids[]' => $this->vocabulary->id(), + 'num' => 3, + 'add_language[]' => ['en'], + 'translate_language[]' => ['ca'], + ]; + $this->drupalPostForm('admin/config/development/generate/term', $edit, 'Generate'); + $this->assertSession()->pageTextNotContains('Deleted'); + $this->assertSession()->pageTextContains('Created 3 new terms'); + $this->assertSession()->pageTextContains('Created 3 term translations'); + // Not using 'kill' so there should be 8 terms. + $terms = \Drupal::entityQuery('taxonomy_term')->execute(); + $this->assertCount(8, $terms); + // Check the translations created (and not created). + $term = Term::load(end($terms)); + $this->assertTrue($term->hasTranslation('ca')); + $this->assertFalse($term->hasTranslation('de')); + $this->assertFalse($term->hasTranslation('fr')); + + // Generate terms in vocabulary 2 only. + $edit = [ + 'vids[]' => $this->vocabulary2->id(), + 'num' => 4, + ]; + $this->drupalPostForm('admin/config/development/generate/term', $edit, 'Generate'); + $this->assertSession()->pageTextContains('Created 4 new terms'); + $this->assertSession()->pageTextNotContains('In vocabulary ' . $this->vocabulary->label()); + $this->assertSession()->pageTextContains('In vocabulary ' . $this->vocabulary2->label()); + // Check the term count in each vocabulary. + $terms1 = \Drupal::entityQuery('taxonomy_term')->condition('vid', $this->vocabulary->id())->execute(); + $this->assertCount(8, $terms1); + $terms2 = \Drupal::entityQuery('taxonomy_term')->condition('vid', $this->vocabulary2->id())->execute(); + $this->assertCount(4, $terms2); + + // Generate in vocabulary 2 with 'kill' to remove the existing vocab2 terms. + $edit = [ + 'vids[]' => $this->vocabulary2->id(), + 'num' => 6, + 'kill' => TRUE, + ]; + $this->drupalPostForm('admin/config/development/generate/term', $edit, 'Generate'); + $this->assertSession()->pageTextContains('Deleted 4 existing terms'); + $this->assertSession()->pageTextContains('Created 6 new terms'); + // Check the term count in vocabulary 1 has not changed. + $terms1 = \Drupal::entityQuery('taxonomy_term')->condition('vid', $this->vocabulary->id())->execute(); + $this->assertCount(8, $terms1); + // Check the term count in vocabulary 2 is just from the second call. + $terms2 = \Drupal::entityQuery('taxonomy_term')->condition('vid', $this->vocabulary2->id())->execute(); + $this->assertCount(6, $terms2); + + // Generate in both vocabularies and specify minimum and maximum depth. + $edit = [ + 'vids[]' => [$this->vocabulary->id(), $this->vocabulary2->id()], + 'num' => 9, + 'minimum_depth' => 2, + 'maximum_depth' => 6, + ]; + $this->drupalPostForm('admin/config/development/generate/term', $edit, 'Generate'); + $this->assertSession()->pageTextContains('Created 9 new terms'); + // Check the total term count is 8 + 6 + 9 = 23. + $terms1 = \Drupal::entityQuery('taxonomy_term')->condition('vid', $this->vocabulary->id())->execute(); + $terms2 = \Drupal::entityQuery('taxonomy_term')->condition('vid', $this->vocabulary2->id())->execute(); + $this->assertCount(23, $terms1 + $terms2); + + } + + /** + * Tests generating vocabularies. + */ + public function testDevelGenerateVocabs() { + $edit = [ + 'num' => 5, + 'title_length' => 12, + 'kill' => TRUE, + ]; + $this->drupalPostForm('admin/config/development/generate/vocabs', $edit, 'Generate'); + $this->assertSession()->pageTextContains('Created the following new vocabularies: '); + $this->assertSession()->pageTextContains('Generate process complete.'); + } + + /** + * Tests generating menus. + * + * @todo Add test coverage to check: + * - title_length is not exceeded. + * - max_depth and max_width work as designed. + * - generating links in existing menus, and then deleting them with kill. + * - using specific link_types settings only create those links. + */ + public function testDevelGenerateMenus() { + $edit = [ + 'num_menus' => 5, + 'num_links' => 7, + ]; + $this->drupalPostForm('admin/config/development/generate/menu', $edit, 'Generate'); + $this->assertSession()->pageTextContains('Created the following 5 new menus: '); + $this->assertSession()->pageTextContains('Created 7 new menu links'); + $this->assertSession()->pageTextContains('Generate process complete.'); + + // Use big numbers for menus and links, but short text, to test for clashes. + // Also verify the kill option. + $edit = [ + 'num_menus' => 160, + 'num_links' => 380, + 'title_length' => 3, + 'kill' => 1, + ]; + $this->drupalPostForm('admin/config/development/generate/menu', $edit, 'Generate'); + $this->assertSession()->pageTextContains('Deleted 5 menu(s) and 0 other link(s).'); + $this->assertSession()->pageTextContains('Created the following 160 new menus: '); + $this->assertSession()->pageTextContains('Created 380 new menu links'); + $this->assertSession()->pageTextContains('Generate process complete.'); + } + + /** + * Tests generating media. + */ + public function testDevelGenerateMedia() { + // As the 'media' plugin has a dependency on 'media' module, the plugin is + // not generating a route to the plugin form. + $this->drupalGet('admin/config/development/generate/media'); + $this->assertSession()->statusCodeEquals(404); + // Enable the module and retry. + \Drupal::service('module_installer')->install(['media']); + $this->getSession()->reload(); + $this->assertSession()->statusCodeEquals(200); + $this->assertSession()->pageTextContains('Generate media'); + + // Create two media types. + $media_type1 = $this->createMediaType('image'); + $media_type2 = $this->createMediaType('audio_file'); + + // Creating media items (non-batch mode). + $edit = [ + 'num' => 5, + 'name_length' => 12, + "media_types[{$media_type1->id()}]" => 1, + "media_types[{$media_type2->id()}]" => 1, + 'kill' => 1, + ]; + $this->drupalPostForm('admin/config/development/generate/media', $edit, 'Generate'); + $this->assertSession()->pageTextContains('Finished creating 5 media items.'); + $this->assertSession()->pageTextContains('Generate process complete.'); + $this->assertCount(5, \Drupal::entityQuery('media')->execute()); + + // Creating media items (batch mode). + $edit = [ + 'num' => 56, + 'name_length' => 6, + "media_types[{$media_type1->id()}]" => 1, + "media_types[{$media_type2->id()}]" => 1, + 'kill' => 1, + ]; + $this->drupalPostForm('admin/config/development/generate/media', $edit, 'Generate'); + $this->assertSession()->pageTextContains('Finished 56 elements created successfully.'); + $this->assertSession()->pageTextContains('Generate process complete.'); + $this->assertCount(56, \Drupal::entityQuery('media')->execute()); + } + + /** + * Tests generating content in batch mode. + */ + public function testDevelGenerateBatchContent() { + // For 50 or more nodes, the processing will be done via batch. + $edit = [ + 'num' => 55, + 'kill' => TRUE, + 'node_types[article]' => TRUE, + 'node_types[page]' => TRUE, + ]; + $this->drupalPostForm('admin/config/development/generate/content', $edit, 'Generate'); + $this->assertSession()->pageTextContains('Finished 55 elements created successfully.'); + $this->assertSession()->pageTextContains('Generate process complete.'); + + // Tests that the expected number of nodes have been created. + $count = count(Node::loadMultiple()); + $this->assertEquals(55, $count, sprintf('The expected total number of nodes is %s, found %s', 55, $count)); + + // Create nodes with translations via batch. + $edit = [ + 'num' => 52, + 'kill' => TRUE, + 'node_types[article]' => TRUE, + 'node_types[page]' => TRUE, + 'add_language[]' => ['en'], + 'translate_language[]' => ['de', 'ca'], + ]; + $this->drupalPostForm('admin/config/development/generate/content', $edit, 'Generate'); + $this->assertCount(52, \Drupal::entityQuery('node')->execute()); + // Only aticles will have translations so get that number. + $articles = \Drupal::entityQuery('node')->condition('type', 'article')->execute(); + $this->assertSession()->pageTextContains(sprintf('Finished 52 elements and %s translations created successfully.', 2 * count($articles))); + + // Generate only articles. + $edit = [ + 'num' => 60, + 'kill' => TRUE, + 'node_types[article]' => TRUE, + 'node_types[page]' => FALSE, + ]; + $this->drupalPostForm('admin/config/development/generate/content', $edit, 'Generate'); + + // Tests that all the created nodes were of the node type selected. + $nodeStorage = $this->container->get('entity_type.manager')->getStorage('node'); + $type = 'article'; + $count = $nodeStorage->getQuery() + ->condition('type', $type) + ->count() + ->execute(); + $this->assertEquals(60, $count, sprintf('The expected number of %s is %s, found %s', $type, 60, $count)); + + } + +} diff --git a/web/modules/contrib/devel/devel_generate/tests/src/Functional/DevelGenerateBrowserTestBase.php b/web/modules/contrib/devel/devel_generate/tests/src/Functional/DevelGenerateBrowserTestBase.php new file mode 100644 index 000000000..6efcbf94e --- /dev/null +++ b/web/modules/contrib/devel/devel_generate/tests/src/Functional/DevelGenerateBrowserTestBase.php @@ -0,0 +1,52 @@ +setUpData(); + } + +} diff --git a/web/modules/contrib/devel/devel_generate/tests/src/Functional/DevelGenerateCommandsTest.php b/web/modules/contrib/devel/devel_generate/tests/src/Functional/DevelGenerateCommandsTest.php new file mode 100644 index 000000000..c68853d40 --- /dev/null +++ b/web/modules/contrib/devel/devel_generate/tests/src/Functional/DevelGenerateCommandsTest.php @@ -0,0 +1,229 @@ +setUpData(); + } + + /** + * Tests generating users. + */ + public function testDrushGenerateUsers() { + // Make sure users get created, and with correct roles. + $this->drush('devel-generate-users', [55], ['kill' => NULL, 'roles' => 'administrator']); + $user = User::load(55); + $this->assertTrue($user->hasRole('administrator')); + } + + /** + * Tests generating terms. + */ + public function testDrushGenerateTerms() { + // Make sure terms get created, and with correct vocab. + $this->drush('devel-generate-terms', [55], ['kill' => NULL, 'bundles' => $this->vocabulary->id()]); + $term = Term::load(55); + $this->assertEquals($this->vocabulary->id(), $term->bundle()); + + // Make sure terms get created, with proper language. + $this->drush('devel-generate-terms', [10], [ + 'kill' => NULL, + 'bundles' => $this->vocabulary->id(), + 'languages' => 'fr', + ]); + $term = Term::load(60); + $this->assertEquals($term->language()->getId(), 'fr'); + + // Make sure terms gets created, with proper translation. + $this->drush('devel-generate-terms', [10], [ + 'kill' => NULL, + 'bundles' => $this->vocabulary->id(), + 'languages' => 'fr', + 'translations' => 'de', + ]); + $term = Term::load(70); + $this->assertTrue($term->hasTranslation('de')); + $this->assertTrue($term->hasTranslation('fr')); + } + + /** + * Tests generating vocabularies. + */ + public function testDrushGenerateVocabs() { + // Make sure vocabs get created. + $this->drush('devel-generate-vocabs', [5], ['kill' => NULL]); + $vocabs = Vocabulary::loadMultiple(); + $this->assertGreaterThan(4, count($vocabs)); + $vocab = array_pop($vocabs); + $this->assertNotEmpty($vocab); + } + + /** + * Tests generating menus. + */ + public function testDrushGenerateMenus() { + // Make sure menus, and with correct properties. + $this->drush('devel-generate-menus', [1, 5], ['kill' => NULL]); + $menus = Menu::loadMultiple(); + foreach ($menus as $menu) { + if (strstr($menu->id(), 'devel-') !== FALSE) { + // We have a menu that we created. + break; + } + } + $link = MenuLinkContent::load(5); + $this->assertEquals($menu->id(), $link->getMenuName()); + } + + /** + * Tests generating content. + */ + public function testDrushGenerateContent() { + // Generate content using the minimum parameters. + $this->drush('devel-generate-content', [21]); + $node = Node::load(21); + $this->assertNotEmpty($node); + + // Make sure articles get comments. Only one third of articles will have + // comment status 'open' and therefore the ability to receive a comment. + // However generating 30 articles will give the likelyhood of test failure + // (i.e. no article gets a comment) as 2/3 ^ 30 = 0.00052% or 1 in 191751. + $this->drush('devel-generate-content', [30, 9], ['kill' => NULL, 'bundles' => 'article']); + $comment = Comment::load(1); + $this->assertNotEmpty($comment); + + // Generate content with a higher number that triggers batch running. + $this->drush('devel-generate-content', [55], ['kill' => NULL]); + $nodes = \Drupal::entityQuery('node')->execute(); + $this->assertCount(55, $nodes); + $messages = $this->getErrorOutput(); + $this->assertStringContainsStringIgnoringCase('Finished 55 elements created successfully.', $messages, 'devel-generate-content batch ending message not found'); + + // Generate content with specified language. + $this->drush('devel-generate-content', [10], ['kill' => NULL, 'languages' => 'fr']); + $nodes = \Drupal::entityQuery('node')->execute(); + $node = Node::load(end($nodes)); + $this->assertEquals($node->language()->getId(), 'fr'); + + // Generate content with translations. + $this->drush('devel-generate-content', [18], [ + 'kill' => NULL, + 'languages' => 'fr', + 'translations' => 'de', + ]); + // Only articles are enabled for translations. + $articles = \Drupal::entityQuery('node')->condition('type', 'article')->execute(); + $pages = \Drupal::entityQuery('node')->condition('type', 'page')->execute(); + $this->assertCount(18, $articles + $pages); + // Check that the last article has 'de' and 'fr' but no 'ca' translation. + $node = Node::load(end($articles)); + $this->assertTrue($node->hasTranslation('de')); + $this->assertTrue($node->hasTranslation('fr')); + $this->assertFalse($node->hasTranslation('ca')); + + // Generate just page content with option --add-type-label. + // Note: Use the -v verbose option to get the ending message shown when not + // generating enough to trigger batch mode. + // @todo Remove -v when the messages are shown for both run types. + $this->drush('devel-generate-content -v', [9], [ + 'kill' => NULL, + 'bundles' => 'page', + 'add-type-label' => NULL, + ]); + // Count the page nodes. + $nodes = \Drupal::entityQuery('node')->condition('type', 'page')->execute(); + $this->assertCount(9, $nodes); + $messages = $this->getErrorOutput(); + $this->assertStringContainsStringIgnoringCase('Created 9 nodes', $messages, 'batch end message not found'); + // Load the final node and verify that the title starts with the label. + $node = Node::load(end($nodes)); + $this->assertEquals('Basic Page - ', substr($node->title->value, 0, 13)); + + // Generate articles with a specified users. + $this->drush('devel-generate-content -v', [10], [ + 'kill' => NULL, + 'bundles' => 'article', + 'authors' => '2', + ]); + // Count the nodes assigned to user 2. We have two other users (0 and 1) so + // if the code was broken and users were assigned randomly the chance that + // this fauly would be detected is 1 - (1/3 ** 10) = 99.998%. + $nodes = \Drupal::entityQuery('node')->condition('type', 'article')->condition('uid', ['2'], 'IN')->execute(); + $this->assertCount(10, $nodes); + + } + + /** + * Tests generating media. + */ + public function testDrushGenerateMedia() { + // Create two media types. + $media_type1 = $this->createMediaType('image'); + $media_type2 = $this->createMediaType('audio_file'); + // Make sure media items gets created with batch process. + $this->drush('devel-generate-media', [53], ['kill' => NULL]); + $this->assertCount(53, \Drupal::entityQuery('media')->execute()); + $messages = $this->getErrorOutput(); + $this->assertStringContainsStringIgnoringCase('Finished 53 elements created successfully.', $messages, 'devel-generate-media batch ending message not found'); + + // Test also with a non-batch process. We're testing also --kill here. + $this->drush('devel-generate-media', [7], [ + 'media-types' => $media_type1->id() . ',' . $media_type2->id(), + 'kill' => NULL, + ]); + $this->assertCount(7, \Drupal::entityQuery('media')->execute()); + } + +} diff --git a/web/modules/contrib/devel/devel_generate/tests/src/Traits/DevelGenerateSetupTrait.php b/web/modules/contrib/devel/devel_generate/tests/src/Traits/DevelGenerateSetupTrait.php new file mode 100644 index 000000000..d388011a9 --- /dev/null +++ b/web/modules/contrib/devel/devel_generate/tests/src/Traits/DevelGenerateSetupTrait.php @@ -0,0 +1,109 @@ +drupalCreateUser([ + 'administer devel_generate', + 'access devel information', + 'access content overview', + ]); + $this->drupalLogin($admin_user); + + $entity_type_manager = $this->container->get('entity_type.manager'); + // Create Basic page and Article node types. + if ($this->profile != 'standard') { + $this->drupalCreateContentType(['type' => 'page', 'name' => 'Basic Page']); + $this->drupalCreateContentType(['type' => 'article', 'name' => 'Article']); + $this->addDefaultCommentField('node', 'article'); + } + + // Enable translation for article content type (but not for page). + \Drupal::service('content_translation.manager')->setEnabled('node', 'article', TRUE); + // Create languages for generated translations. + ConfigurableLanguage::createFromLangcode('ca')->save(); + ConfigurableLanguage::createFromLangcode('de')->save(); + ConfigurableLanguage::createFromLangcode('fr')->save(); + + // Creating a vocabulary to associate taxonomy terms generated. + $this->vocabulary = Vocabulary::create([ + 'name' => 'Vocab 1 ' . $this->randomString(15), + 'description' => $this->randomMachineName(), + 'vid' => 'vocab_1_' . mb_strtolower($this->randomMachineName()), + 'langcode' => Language::LANGCODE_NOT_SPECIFIED, + ]); + $this->vocabulary->save(); + // Enable translation for terms in this vocabulary. + \Drupal::service('content_translation.manager')->setEnabled('taxonomy_term', $this->vocabulary->id(), TRUE); + + // Creates a field of an entity reference field storage on article. + $field_name = 'taxonomy_' . $this->vocabulary->id(); + + $handler_settings = [ + 'target_bundles' => [ + $this->vocabulary->id() => $this->vocabulary->id(), + ], + 'auto_create' => TRUE, + ]; + $this->createEntityReferenceField('node', 'article', $field_name, NULL, 'taxonomy_term', 'default', $handler_settings, FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED); + + $entity_type_manager->getStorage('entity_form_display') + ->load('node.article.default') + ->setComponent($field_name, [ + 'type' => 'options_select', + ]) + ->save(); + + $entity_type_manager->getStorage('entity_view_display') + ->load('node.article.default') + ->setComponent($field_name, [ + 'type' => 'entity_reference_label', + ]) + ->save(); + + // Create the second vocabulary. + $this->vocabulary2 = Vocabulary::create([ + 'name' => 'Vocab 2 ' . $this->randomString(15), + 'vid' => 'vocab_2_' . mb_strtolower($this->randomMachineName()), + 'langcode' => Language::LANGCODE_NOT_SPECIFIED, + ]); + $this->vocabulary2->save(); + + } + +} diff --git a/web/modules/contrib/devel/devel_generate/tests/src/Unit/DevelGenerateManagerTest.php b/web/modules/contrib/devel/devel_generate/tests/src/Unit/DevelGenerateManagerTest.php new file mode 100644 index 000000000..ca9bcd306 --- /dev/null +++ b/web/modules/contrib/devel/devel_generate/tests/src/Unit/DevelGenerateManagerTest.php @@ -0,0 +1,86 @@ + [ + 'id' => 'devel_generate_example', + 'class' => 'Drupal\devel_generate_example\Plugin\DevelGenerate\ExampleDevelGenerate', + 'url' => 'devel_generate_example', + 'dependencies' => [], + ], + ]; + + /** + * {@inheritdoc} + */ + protected function setUp() { + parent::setUp(); + // Mock a Discovery object to replace AnnotationClassDiscovery. + $this->discovery = $this->createMock('Drupal\Component\Plugin\Discovery\DiscoveryInterface'); + $this->discovery->expects($this->any()) + ->method('getDefinitions') + ->will($this->returnValue($this->definitions)); + + } + + /** + * Test creating an instance of the DevelGenerateManager. + */ + public function testCreateInstance() { + $namespaces = new \ArrayObject(['Drupal\devel_generate_example' => realpath(dirname(__FILE__) . '/../../../modules/devel_generate_example/lib')]); + $cache_backend = $this->createMock('Drupal\Core\Cache\CacheBackendInterface'); + + $module_handler = $this->createMock('Drupal\Core\Extension\ModuleHandlerInterface'); + $manager = new TestDevelGeneratePluginManager($namespaces, $cache_backend, $module_handler); + $manager->setDiscovery($this->discovery); + + $example_instance = $manager->createInstance('devel_generate_example'); + $plugin_def = $example_instance->getPluginDefinition(); + + $this->assertInstanceOf('Drupal\devel_generate_example\Plugin\DevelGenerate\ExampleDevelGenerate', $example_instance); + $this->assertArrayHasKey('url', $plugin_def); + $this->assertTrue($plugin_def['url'] == 'devel_generate_example'); + } + +} + +/** + * Provides a testing version of DevelGeneratePluginManager with an empty + * constructor. + */ +class TestDevelGeneratePluginManager extends DevelGeneratePluginManager { + + /** + * Sets the discovery for the manager. + * + * @param \Drupal\Component\Plugin\Discovery\DiscoveryInterface $discovery + * The discovery object. + */ + public function setDiscovery(DiscoveryInterface $discovery) { + $this->discovery = $discovery; + } + +} diff --git a/web/modules/contrib/devel/drush.services.yml b/web/modules/contrib/devel/drush.services.yml new file mode 100644 index 000000000..4b4426289 --- /dev/null +++ b/web/modules/contrib/devel/drush.services.yml @@ -0,0 +1,6 @@ +services: + devel.commands: + class: Drupal\devel\Commands\DevelCommands + arguments: ['@token', '@service_container', '@event_dispatcher', '@module_handler'] + tags: + - { name: drush.command } diff --git a/web/modules/contrib/devel/icons/bebebe/cog.svg b/web/modules/contrib/devel/icons/bebebe/cog.svg new file mode 100644 index 000000000..5f57a3922 --- /dev/null +++ b/web/modules/contrib/devel/icons/bebebe/cog.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/web/modules/contrib/devel/icons/ffffff/cog.svg b/web/modules/contrib/devel/icons/ffffff/cog.svg new file mode 100644 index 000000000..63b300e36 --- /dev/null +++ b/web/modules/contrib/devel/icons/ffffff/cog.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/web/modules/contrib/devel/icons/folder.png b/web/modules/contrib/devel/icons/folder.png new file mode 100644 index 0000000000000000000000000000000000000000..ba8e7a8031320fe3ab8c802cdf332d44203c8852 GIT binary patch literal 138695 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%0D~T?$1*FwGj`n@!iGIaLz{)XTUH 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 + + . + + + + + + + + + + + + + + + + + _ignore + \.patch + interdif + + + + + + \.md + + + + Commands\.php + + + + + + 0 + + + diff --git a/web/modules/contrib/devel/src/Annotation/DevelDumper.php b/web/modules/contrib/devel/src/Annotation/DevelDumper.php new file mode 100644 index 000000000..40fe02794 --- /dev/null +++ b/web/modules/contrib/devel/src/Annotation/DevelDumper.php @@ -0,0 +1,37 @@ +token = $token; + $this->container = $container; + $this->eventDispatcher = $eventDispatcher; + $this->moduleHandler = $moduleHandler; + } + + /** + * @return \Drupal\Core\Extension\ModuleHandlerInterface + * The moduleHandler. + */ + public function getModuleHandler() { + return $this->moduleHandler; + } + + /** + * @return mixed + * The eventDispatcher. + */ + public function getEventDispatcher() { + return $this->eventDispatcher; + } + + /** + * @return mixed + * The container. + */ + public function getContainer() { + return $this->container; + } + + /** + * @return \Drupal\Core\Utility\Token + * The token. + */ + public function getToken() { + return $this->token; + } + + /** + * Uninstall, and Install modules. + * + * @command devel:reinstall + * @aliases dre,devel-reinstall + * @allow-additional-options pm-uninstall,pm-enable + * + * @param string $modules + * A comma-separated list of module names. + */ + public function reinstall($modules) { + $modules = StringUtils::csvToArray($modules); + + $modules_str = implode(',', $modules); + $process = $this->processManager()->drush($this->siteAliasManager()->getSelf(), 'pm:uninstall', [$modules_str]); + $process->mustRun(); + $process = $this->processManager()->drush($this->siteAliasManager()->getSelf(), 'pm:enable', [$modules_str]); + $process->mustRun(); + } + + /** + * List implementations of a given hook and optionally edit one. + * + * @command devel:hook + * + * @param string $hook + * The name of the hook to explore. + * @param string $implementation + * The name of the implementation to edit. Usually omitted. + * + * @usage devel-hook cron + * List implementations of hook_cron(). + * @aliases fnh,fn-hook,hook,devel-hook + * @optionset_get_editor + */ + public function hook($hook, $implementation) { + // Get implementations in the .install files as well. + include_once './core/includes/install.inc'; + drupal_load_updates(); + $info = $this->codeLocate($implementation . "_$hook"); + $exec = self::getEditor(); + $cmd = sprintf($exec, Escape::shellArg($info['file'])); + $process = $this->processManager()->shell($cmd); + $process->setTty(TRUE); + $process->mustRun(); + } + + /** + * @hook interact hook + */ + public function hookInteract(Input $input, Output $output) { + if (!$input->getArgument('implementation')) { + if ($hook_implementations = $this->getModuleHandler()->getImplementations($input->getArgument('hook'))) { + if (!$choice = $this->io()->choice('Enter the number of the hook implementation you wish to view.', array_combine($hook_implementations, $hook_implementations))) { + throw new UserAbortException(); + } + $input->setArgument('implementation', $choice); + } + else { + throw new \Exception(dt('No implementations')); + } + } + } + + /** + * List implementations of a given event and optionally edit one. + * + * @command devel:event + * + * @param string $event + * The name of the event to explore. If omitted, a list of events is shown. + * @param string $implementation + * The name of the implementation to show. Usually omitted. + * + * @usage devel-event + * Pick a Kernel event, then pick an implementation, and then view its + * source code. + * @usage devel-event kernel.terminate + * Pick a terminate subscribers implementation and view its source code. + * @aliases fne,fn-event,event + */ + public function event($event, $implementation) { + $info = $this->codeLocate($implementation); + $exec = self::getEditor(); + $cmd = sprintf($exec, Escape::shellArg($info['file'])); + $process = $this->processManager()->shell($cmd); + $process->setTty(TRUE); + $process->mustRun(); + } + + /** + * @hook interact devel:event + */ + public function interactEvent(Input $input, Output $output) { + $dispatcher = $this->getEventDispatcher(); + $event = $input->getArgument('event'); + if (!$event) { + // @todo Expand this list. + $events = [ + 'kernel.controller', + 'kernel.exception', + 'kernel.request', + 'kernel.response', + 'kernel.terminate', + 'kernel.view', + ]; + $events = array_combine($events, $events); + if (!$event = $this->io()->choice('Enter the event you wish to explore.', $events)) { + throw new UserAbortException(); + } + $input->setArgument('event', $event); + } + if ($implementations = $dispatcher->getListeners($event)) { + foreach ($implementations as $implementation) { + $callable = get_class($implementation[0]) . '::' . $implementation[1]; + $choices[$callable] = $callable; + } + if (!$choice = $this->io()->choice('Enter the number of the implementation you wish to view.', $choices)) { + throw new UserAbortException(); + } + $input->setArgument('implementation', $choice); + } + else { + throw new \Exception(dt('No implementations.')); + } + } + + /** + * List available tokens. + * + * @command devel:token + * @aliases token,devel-token + * @field-labels + * group: Group + * token: Token + * name: Name + * @default-fields group,token,name + * + * @return \Consolidation\OutputFormatters\StructuredData\RowsOfFields + * The tokens structured in a RowsOfFields object. + */ + public function token($options = ['format' => 'table']) { + $all = $this->getToken()->getInfo(); + foreach ($all['tokens'] as $group => $tokens) { + foreach ($tokens as $key => $token) { + $rows[] = [ + 'group' => $group, + 'token' => $key, + 'name' => $token['name'], + ]; + } + } + return new RowsOfFields($rows); + } + + /** + * Generate a Universally Unique Identifier (UUID). + * + * @command devel:uuid + * @aliases uuid,devel-uuid + * @usage drush devel-uuid + * + * @return string + * The generated uuid. + */ + public function uuid() { + $uuid = new Php(); + return $uuid->generate(); + } + + /** + * Get source code line for specified function or method. + */ + public function codeLocate($function_name) { + // Get implementations in the .install files as well. + include_once './core/includes/install.inc'; + drupal_load_updates(); + + if (strpos($function_name, '::') === FALSE) { + if (!function_exists($function_name)) { + throw new \Exception(dt('Function not found')); + } + $reflect = new \ReflectionFunction($function_name); + } + else { + list($class, $method) = explode('::', $function_name); + if (!method_exists($class, $method)) { + throw new \Exception(dt('Method not found')); + } + $reflect = new \ReflectionMethod($class, $method); + } + return [ + 'file' => $reflect->getFileName(), + 'startline' => $reflect->getStartLine(), + 'endline' => $reflect->getEndLine(), + ]; + + } + + /** + * Get a list of available container services. + * + * @command devel:services + * + * @param string $prefix + * Optional prefix to filter the service list by. + * @param array $options + * An array of options (is this used?) + * + * @aliases devel-container-services,dcs,devel-services + * @usage drush devel-services + * Gets a list of all available container services + * @usage drush dcs plugin.manager + * Get all services containing "plugin.manager" + * + * @return array + * The container service ids. + */ + public function services($prefix = NULL, array $options = ['format' => 'yaml']) { + $container = $this->getContainer(); + + // Get a list of all available service IDs. + $services = $container->getServiceIds(); + + // If there is a prefix, try to find matches. + if (isset($prefix)) { + $services = preg_grep("/$prefix/", $services); + } + + if (empty($services)) { + throw new \Exception(dt('No container services found.')); + } + + sort($services); + return $services; + } + +} diff --git a/web/modules/contrib/devel/src/Controller/ContainerInfoController.php b/web/modules/contrib/devel/src/Controller/ContainerInfoController.php new file mode 100644 index 000000000..9aac792d1 --- /dev/null +++ b/web/modules/contrib/devel/src/Controller/ContainerInfoController.php @@ -0,0 +1,257 @@ +kernel = $drupalKernel; + $this->dumper = $dumper; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('kernel'), + $container->get('devel.dumper') + ); + } + + /** + * Builds the services overview page. + * + * @return array + * A render array as expected by the renderer. + */ + public function serviceList() { + $headers = [ + $this->t('ID'), + $this->t('Class'), + $this->t('Alias'), + $this->t('Operations'), + ]; + + $rows = []; + + if ($container = $this->kernel->getCachedContainerDefinition()) { + foreach ($container['services'] as $service_id => $definition) { + $service = unserialize($definition); + + $row['id'] = [ + 'data' => $service_id, + 'filter' => TRUE, + ]; + $row['class'] = [ + 'data' => isset($service['class']) ? $service['class'] : '', + 'filter' => TRUE, + ]; + $row['alias'] = [ + 'data' => array_search($service_id, $container['aliases']) ?: '', + 'filter' => TRUE, + ]; + $row['operations']['data'] = [ + '#type' => 'operations', + '#links' => [ + 'devel' => [ + 'title' => $this->t('Devel'), + 'url' => Url::fromRoute('devel.container_info.service.detail', ['service_id' => $service_id]), + 'attributes' => [ + 'class' => ['use-ajax'], + 'data-dialog-type' => 'modal', + 'data-dialog-options' => Json::encode([ + 'width' => 700, + 'minHeight' => 500, + ]), + ], + ], + ], + ]; + + $rows[$service_id] = $row; + } + + ksort($rows); + } + + $output['services'] = [ + '#type' => 'devel_table_filter', + '#filter_label' => $this->t('Search'), + '#filter_placeholder' => $this->t('Enter service id, alias or class'), + '#filter_description' => $this->t('Enter a part of the service id, service alias or class to filter by.'), + '#header' => $headers, + '#rows' => $rows, + '#empty' => $this->t('No services found.'), + '#sticky' => TRUE, + '#attributes' => [ + 'class' => ['devel-service-list'], + ], + ]; + + return $output; + } + + /** + * Returns a render array representation of the service. + * + * @param string $service_id + * The ID of the service to retrieve. + * + * @return array + * A render array containing the service detail. + * + * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException + * If the requested service is not defined. + */ + public function serviceDetail($service_id) { + $instance = $this->container->get($service_id, ContainerInterface::NULL_ON_INVALID_REFERENCE); + if ($instance === NULL) { + throw new NotFoundHttpException(); + } + + $output = []; + + if ($cached_definitions = $this->kernel->getCachedContainerDefinition()) { + // Tries to retrieve the service definition from the kernel's cached + // container definition. + if (isset($cached_definitions['services'][$service_id])) { + $definition = unserialize($cached_definitions['services'][$service_id]); + + // If the service has an alias add it to the definition. + if ($alias = array_search($service_id, $cached_definitions['aliases'])) { + $definition['alias'] = $alias; + } + + $output['definition'] = $this->dumper->exportAsRenderable($definition, $this->t('Computed Definition')); + } + } + + $output['instance'] = $this->dumper->exportAsRenderable($instance, $this->t('Instance')); + + return $output; + } + + /** + * Builds the parameters overview page. + * + * @return array + * A render array as expected by the renderer. + */ + public function parameterList() { + $headers = [ + $this->t('Name'), + $this->t('Operations'), + ]; + + $rows = []; + + if ($container = $this->kernel->getCachedContainerDefinition()) { + foreach ($container['parameters'] as $parameter_name => $definition) { + $row['name'] = [ + 'data' => $parameter_name, + 'filter' => TRUE, + ]; + $row['operations']['data'] = [ + '#type' => 'operations', + '#links' => [ + 'devel' => [ + 'title' => $this->t('Devel'), + 'url' => Url::fromRoute('devel.container_info.parameter.detail', ['parameter_name' => $parameter_name]), + 'attributes' => [ + 'class' => ['use-ajax'], + 'data-dialog-type' => 'modal', + 'data-dialog-options' => Json::encode([ + 'width' => 700, + 'minHeight' => 500, + ]), + ], + ], + ], + ]; + + $rows[$parameter_name] = $row; + } + + ksort($rows); + } + + $output['parameters'] = [ + '#type' => 'devel_table_filter', + '#filter_label' => $this->t('Search'), + '#filter_placeholder' => $this->t('Enter parameter name'), + '#filter_description' => $this->t('Enter a part of the parameter name to filter by.'), + '#header' => $headers, + '#rows' => $rows, + '#empty' => $this->t('No parameters found.'), + '#sticky' => TRUE, + '#attributes' => [ + 'class' => ['devel-parameter-list'], + ], + ]; + + return $output; + } + + /** + * Returns a render array representation of the parameter value. + * + * @param string $parameter_name + * The name of the parameter to retrieve. + * + * @return array + * A render array containing the parameter value. + * + * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException + * If the requested parameter is not defined. + */ + public function parameterDetail($parameter_name) { + try { + $parameter = $this->container->getParameter($parameter_name); + } + catch (ParameterNotFoundException $e) { + throw new NotFoundHttpException(); + } + + return $this->dumper->exportAsRenderable($parameter); + } + +} diff --git a/web/modules/contrib/devel/src/Controller/DevelController.php b/web/modules/contrib/devel/src/Controller/DevelController.php new file mode 100644 index 000000000..d57a07f4e --- /dev/null +++ b/web/modules/contrib/devel/src/Controller/DevelController.php @@ -0,0 +1,226 @@ +dumper = $dumper; + $this->entityTypeBundleInfo = $entity_type_bundle_info; + $this->fieldTypeManager = $field_type_manager; + $this->formatterPluginManager = $formatter_plugin_manager; + $this->widgetPluginManager = $widget_plugin_manager; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('devel.dumper'), + $container->get('entity_type.bundle.info'), + $container->get('plugin.manager.field.field_type'), + $container->get('plugin.manager.field.formatter'), + $container->get('plugin.manager.field.widget') + ); + } + + /** + * Clears all caches, then redirects to the previous page. + */ + public function cacheClear() { + drupal_flush_all_caches(); + $this->messenger()->addMessage($this->t('Cache cleared.')); + return $this->redirect(''); + } + + /** + * Theme registry. + * + * @return array + * The complete theme registry as renderable. + */ + public function themeRegistry() { + $hooks = theme_get_registry(); + ksort($hooks); + return $this->dumper->exportAsRenderable($hooks); + } + + /** + * Builds the fields info overview page. + * + * @return array + * Array of page elements to render. + */ + public function fieldInfoPage() { + $fields = FieldStorageConfig::loadMultiple(); + ksort($fields); + $output['fields'] = $this->dumper->exportAsRenderable($fields, $this->t('Fields')); + + $field_instances = FieldConfig::loadMultiple(); + ksort($field_instances); + $output['instances'] = $this->dumper->exportAsRenderable($field_instances, $this->t('Instances')); + + $bundles = $this->entityTypeBundleInfo->getAllBundleInfo(); + ksort($bundles); + $output['bundles'] = $this->dumper->exportAsRenderable($bundles, $this->t('Bundles')); + + $field_types = $this->fieldTypeManager->getUiDefinitions(); + ksort($field_types); + $output['field_types'] = $this->dumper->exportAsRenderable($field_types, $this->t('Field types')); + + $formatter_types = $this->formatterPluginManager->getDefinitions(); + ksort($formatter_types); + $output['formatter_types'] = $this->dumper->exportAsRenderable($formatter_types, $this->t('Formatter types')); + + $widget_types = $this->widgetPluginManager->getDefinitions(); + ksort($widget_types); + $output['widget_types'] = $this->dumper->exportAsRenderable($widget_types, $this->t('Widget types')); + + return $output; + } + + /** + * Builds the state variable overview page. + * + * @return array + * Array of page elements to render. + */ + public function stateSystemPage() { + $can_edit = $this->currentUser()->hasPermission('administer site configuration'); + + $header = [ + 'name' => $this->t('Name'), + 'value' => $this->t('Value'), + ]; + + if ($can_edit) { + $header['edit'] = $this->t('Operations'); + } + + $rows = []; + // State class doesn't have getAll method so we get all states from the + // KeyValueStorage. + foreach ($this->keyValue('state')->getAll() as $state_name => $state) { + $rows[$state_name] = [ + 'name' => [ + 'data' => $state_name, + 'class' => 'table-filter-text-source', + ], + 'value' => [ + 'data' => $this->dumper->export($state), + ], + ]; + + if ($can_edit) { + $operations['edit'] = [ + 'title' => $this->t('Edit'), + 'url' => Url::fromRoute('devel.system_state_edit', ['state_name' => $state_name]), + ]; + $rows[$state_name]['edit'] = [ + 'data' => ['#type' => 'operations', '#links' => $operations], + ]; + } + } + + $output['states'] = [ + '#type' => 'devel_table_filter', + '#filter_label' => $this->t('Search'), + '#filter_placeholder' => $this->t('Enter state name'), + '#filter_title' => $this->t('Enter a part of the state name to filter by.'), + '#header' => $header, + '#rows' => $rows, + '#empty' => $this->t('No state variables found.'), + '#attributes' => [ + 'class' => ['devel-state-list'], + ], + ]; + + return $output; + } + + /** + * Builds the session overview page. + * + * @return array + * Array of page elements to render. + */ + public function session() { + $output['description'] = [ + '#markup' => '

' . $this->t('Here are the contents of your $_SESSION variable.') . '

', + ]; + $output['session'] = [ + '#type' => 'table', + '#header' => [$this->t('Session name'), $this->t('Session ID')], + '#rows' => [[session_name(), session_id()]], + '#empty' => $this->t('No session available.'), + ]; + $output['data'] = $this->dumper->exportAsRenderable($_SESSION); + + return $output; + } + +} diff --git a/web/modules/contrib/devel/src/Controller/ElementInfoController.php b/web/modules/contrib/devel/src/Controller/ElementInfoController.php new file mode 100644 index 000000000..14ae3058f --- /dev/null +++ b/web/modules/contrib/devel/src/Controller/ElementInfoController.php @@ -0,0 +1,145 @@ +elementInfo = $element_info; + $this->dumper = $dumper; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('element_info'), + $container->get('devel.dumper') + ); + } + + /** + * Builds the element overview page. + * + * @return array + * A render array as expected by the renderer. + */ + public function elementList() { + $headers = [ + $this->t('Name'), + $this->t('Provider'), + $this->t('Class'), + $this->t('Operations'), + ]; + + $rows = []; + + foreach ($this->elementInfo->getDefinitions() as $element_type => $definition) { + $row['name'] = [ + 'data' => $element_type, + 'filter' => TRUE, + ]; + $row['provider'] = [ + 'data' => $definition['provider'], + 'filter' => TRUE, + ]; + $row['class'] = [ + 'data' => $definition['class'], + 'filter' => TRUE, + ]; + $row['operations']['data'] = [ + '#type' => 'operations', + '#links' => [ + 'devel' => [ + 'title' => $this->t('Devel'), + 'url' => Url::fromRoute('devel.elements_page.detail', ['element_name' => $element_type]), + 'attributes' => [ + 'class' => ['use-ajax'], + 'data-dialog-type' => 'modal', + 'data-dialog-options' => Json::encode([ + 'width' => 700, + 'minHeight' => 500, + ]), + ], + ], + ], + ]; + + $rows[$element_type] = $row; + } + + ksort($rows); + + $output['elements'] = [ + '#type' => 'devel_table_filter', + '#filter_label' => $this->t('Search'), + '#filter_placeholder' => $this->t('Enter element id, provider or class'), + '#filter_description' => $this->t('Enter a part of the element id, provider or class to filter by.'), + '#header' => $headers, + '#rows' => $rows, + '#empty' => $this->t('No elements found.'), + '#sticky' => TRUE, + '#attributes' => [ + 'class' => ['devel-element-list'], + ], + ]; + + return $output; + } + + /** + * Returns a render array representation of the element. + * + * @param string $element_name + * The name of the element to retrieve. + * + * @return array + * A render array containing the element. + * + * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException + * If the requested element is not defined. + */ + public function elementDetail($element_name) { + if (!$element = $this->elementInfo->getDefinition($element_name, FALSE)) { + throw new NotFoundHttpException(); + } + + $element += $this->elementInfo->getInfo($element_name); + return $this->dumper->exportAsRenderable($element, $element_name); + } + +} diff --git a/web/modules/contrib/devel/src/Controller/EntityDebugController.php b/web/modules/contrib/devel/src/Controller/EntityDebugController.php new file mode 100644 index 000000000..f999e458f --- /dev/null +++ b/web/modules/contrib/devel/src/Controller/EntityDebugController.php @@ -0,0 +1,142 @@ +dumper = $dumper; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static($container->get('devel.dumper')); + } + + /** + * Returns the entity type definition of the current entity. + * + * @param \Drupal\Core\Routing\RouteMatchInterface $route_match + * A RouteMatch object. + * + * @return array + * Array of page elements to render. + */ + public function entityTypeDefinition(RouteMatchInterface $route_match) { + $output = []; + + $entity = $this->getEntityFromRouteMatch($route_match); + + if ($entity instanceof EntityInterface) { + $output = $this->dumper->exportAsRenderable($entity->getEntityType()); + } + + return $output; + } + + /** + * Returns the loaded structure of the current entity. + * + * @param \Drupal\Core\Routing\RouteMatchInterface $route_match + * A RouteMatch object. + * + * @return array + * Array of page elements to render. + */ + public function entityLoad(RouteMatchInterface $route_match) { + $output = []; + + $entity = $this->getEntityFromRouteMatch($route_match); + + if ($entity instanceof EntityInterface) { + // Field definitions are lazy loaded and are populated only when needed. + // By calling ::getFieldDefinitions() we are sure that field definitions + // are populated and available in the dump output. + // @see https://www.drupal.org/node/2311557 + if ($entity instanceof FieldableEntityInterface) { + $entity->getFieldDefinitions(); + } + + $output = $this->dumper->exportAsRenderable($entity); + } + + return $output; + } + + /** + * Returns the render structure of the current entity. + * + * @param \Drupal\Core\Routing\RouteMatchInterface $route_match + * A RouteMatch object. + * + * @return array + * Array of page elements to render. + */ + public function entityRender(RouteMatchInterface $route_match) { + $output = []; + + $entity = $this->getEntityFromRouteMatch($route_match); + + if ($entity instanceof EntityInterface) { + $entity_type_id = $entity->getEntityTypeId(); + $view_hook = $entity_type_id . '_view'; + + $build = []; + // If module implements own {entity_type}_view() hook use it, otherwise + // fallback to the entity view builder if available. + if (function_exists($view_hook)) { + $build = $view_hook($entity); + } + elseif ($this->entityTypeManager()->hasHandler($entity_type_id, 'view_builder')) { + $build = $this->entityTypeManager()->getViewBuilder($entity_type_id)->view($entity); + } + + $output = $this->dumper->exportAsRenderable($build); + } + + return $output; + } + + /** + * Retrieves entity from route match. + * + * @param \Drupal\Core\Routing\RouteMatchInterface $route_match + * The route match. + * + * @return \Drupal\Core\Entity\EntityInterface|null + * The entity object as determined from the passed-in route match. + */ + protected function getEntityFromRouteMatch(RouteMatchInterface $route_match) { + $parameter_name = $route_match->getRouteObject()->getOption('_devel_entity_type_id'); + return $route_match->getParameter($parameter_name); + } + +} diff --git a/web/modules/contrib/devel/src/Controller/EntityTypeInfoController.php b/web/modules/contrib/devel/src/Controller/EntityTypeInfoController.php new file mode 100644 index 000000000..42cbee6f5 --- /dev/null +++ b/web/modules/contrib/devel/src/Controller/EntityTypeInfoController.php @@ -0,0 +1,180 @@ +dumper = $dumper; + $this->entityLastInstalledSchemaRepository = $entityLastInstalledSchemaRepository; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('devel.dumper'), + $container->get('entity.last_installed_schema.repository') + ); + } + + /** + * Builds the entity types overview page. + * + * @return array + * A render array as expected by the renderer. + */ + public function entityTypeList() { + $headers = [ + $this->t('ID'), + $this->t('Name'), + $this->t('Provider'), + $this->t('Class'), + $this->t('Operations'), + ]; + + $rows = []; + + foreach ($this->entityTypeManager()->getDefinitions() as $entity_type_id => $entity_type) { + $row['id'] = [ + 'data' => $entity_type->id(), + 'filter' => TRUE, + ]; + $row['name'] = [ + 'data' => $entity_type->getLabel(), + 'filter' => TRUE, + ]; + $row['provider'] = [ + 'data' => $entity_type->getProvider(), + 'filter' => TRUE, + ]; + $row['class'] = [ + 'data' => $entity_type->getClass(), + 'filter' => TRUE, + ]; + $row['operations']['data'] = [ + '#type' => 'operations', + '#links' => [ + 'devel' => [ + 'title' => $this->t('Devel'), + 'url' => Url::fromRoute('devel.entity_info_page.detail', ['entity_type_id' => $entity_type_id]), + 'attributes' => [ + 'class' => ['use-ajax'], + 'data-dialog-type' => 'modal', + 'data-dialog-options' => Json::encode([ + 'width' => 700, + 'minHeight' => 500, + ]), + ], + ], + 'fields' => [ + 'title' => $this->t('Fields'), + 'url' => Url::fromRoute('devel.entity_info_page.fields', ['entity_type_id' => $entity_type_id]), + 'attributes' => [ + 'class' => ['use-ajax'], + 'data-dialog-type' => 'modal', + 'data-dialog-options' => Json::encode([ + 'width' => 700, + 'minHeight' => 500, + ]), + ], + ], + ], + ]; + + $rows[$entity_type_id] = $row; + } + + ksort($rows); + + $output['entities'] = [ + '#type' => 'devel_table_filter', + '#filter_label' => $this->t('Search'), + '#filter_placeholder' => $this->t('Enter entity type id, provider or class'), + '#filter_description' => $this->t('Enter a part of the entity type id, provider or class to filter by.'), + '#header' => $headers, + '#rows' => $rows, + '#empty' => $this->t('No entity types found.'), + '#sticky' => TRUE, + '#attributes' => [ + 'class' => ['devel-entity-type-list'], + ], + ]; + + return $output; + } + + /** + * Returns a render array representation of the entity type. + * + * @param string $entity_type_id + * The name of the entity type to retrieve. + * + * @return array + * A render array containing the entity type. + * + * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException + * If the requested entity type is not defined. + */ + public function entityTypeDetail($entity_type_id) { + if (!$entity_type = $this->entityTypeManager()->getDefinition($entity_type_id, FALSE)) { + throw new NotFoundHttpException(); + } + + return $this->dumper->exportAsRenderable($entity_type, $entity_type_id); + } + + /** + * Returns a render array representation of the entity type field definitions. + * + * @param string $entity_type_id + * The name of the entity type to retrieve. + * + * @return array + * A render array containing the entity type field definitions. + * + * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException + * If the requested entity type is not defined. + */ + public function entityTypeFields($entity_type_id) { + if (!$this->entityTypeManager()->getDefinition($entity_type_id, FALSE)) { + throw new NotFoundHttpException(); + } + + $field_storage_definitions = $this->entityLastInstalledSchemaRepository->getLastInstalledFieldStorageDefinitions($entity_type_id); + return $this->dumper->exportAsRenderable($field_storage_definitions, $entity_type_id); + } + +} diff --git a/web/modules/contrib/devel/src/Controller/EventInfoController.php b/web/modules/contrib/devel/src/Controller/EventInfoController.php new file mode 100644 index 000000000..3f6a251a7 --- /dev/null +++ b/web/modules/contrib/devel/src/Controller/EventInfoController.php @@ -0,0 +1,121 @@ +eventDispatcher = $event_dispatcher; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('event_dispatcher') + ); + } + + /** + * Builds the events overview page. + * + * @return array + * A render array as expected by the renderer. + */ + public function eventList() { + $headers = [ + 'name' => [ + 'data' => $this->t('Event Name'), + 'class' => 'visually-hidden', + ], + 'callable' => $this->t('Callable'), + 'priority' => $this->t('Priority'), + ]; + + $event_listeners = $this->eventDispatcher->getListeners(); + ksort($event_listeners); + + $rows = []; + + foreach ($event_listeners as $event_name => $listeners) { + + $rows[][] = [ + 'data' => $event_name, + 'class' => ['devel-event-name-header'], + 'filter' => TRUE, + 'colspan' => '3', + 'header' => TRUE, + ]; + + foreach ($listeners as $listener) { + $row['name'] = [ + 'data' => $event_name, + 'class' => ['visually-hidden'], + 'filter' => TRUE, + ]; + $row['class'] = [ + 'data' => $this->resolveCallableName($listener), + ]; + $row['priority'] = [ + 'data' => $this->eventDispatcher->getListenerPriority($event_name, $listener), + ]; + $rows[] = $row; + } + } + + $output['events'] = [ + '#type' => 'devel_table_filter', + '#filter_label' => $this->t('Search'), + '#filter_placeholder' => $this->t('Enter event name'), + '#filter_description' => $this->t('Enter a part of the event name to filter by.'), + '#header' => $headers, + '#rows' => $rows, + '#empty' => $this->t('No events found.'), + '#attributes' => [ + 'class' => ['devel-event-list'], + ], + ]; + + return $output; + } + + /** + * Helper function for resolve callable name. + * + * @param mixed $callable + * The for which resolve the name. Can be either the name of a function + * stored in a string variable, or an object and the name of a method + * within the object. + * + * @return string + * The resolved callable name or an empty string. + */ + protected function resolveCallableName($callable) { + if (is_callable($callable, TRUE, $callable_name)) { + return $callable_name; + } + return ''; + } + +} diff --git a/web/modules/contrib/devel/src/Controller/LayoutInfoController.php b/web/modules/contrib/devel/src/Controller/LayoutInfoController.php new file mode 100644 index 000000000..4fdb3f419 --- /dev/null +++ b/web/modules/contrib/devel/src/Controller/LayoutInfoController.php @@ -0,0 +1,82 @@ +layoutPluginManager = $pluginManagerLayout; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('plugin.manager.core.layout') + ); + } + + /** + * Builds the Layout Info page. + * + * @return array + * Array of page elements to render. + */ + public function layoutInfoPage() { + $headers = [ + $this->t('Icon'), + $this->t('Label'), + $this->t('Description'), + $this->t('Category'), + $this->t('Regions'), + $this->t('Provider'), + ]; + + $rows = []; + + foreach ($this->layoutPluginManager->getDefinitions() as $layout) { + $rows[] = [ + 'icon' => ['data' => $layout->getIcon()], + 'label' => $layout->getLabel(), + 'description' => $layout->getDescription(), + 'category' => $layout->getCategory(), + 'regions' => implode(', ', $layout->getRegionLabels()), + 'provider' => $layout->getProvider(), + ]; + } + + $output['layouts'] = [ + '#type' => 'table', + '#header' => $headers, + '#rows' => $rows, + '#empty' => $this->t('No layouts available.'), + '#attributes' => [ + 'class' => ['devel-layout-list'], + ], + ]; + + return $output; + } + +} diff --git a/web/modules/contrib/devel/src/Controller/RouteInfoController.php b/web/modules/contrib/devel/src/Controller/RouteInfoController.php new file mode 100644 index 000000000..a87aa5c95 --- /dev/null +++ b/web/modules/contrib/devel/src/Controller/RouteInfoController.php @@ -0,0 +1,195 @@ +routeProvider = $provider; + $this->router = $router; + $this->dumper = $dumper; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('router.route_provider'), + $container->get('router.no_access_checks'), + $container->get('devel.dumper') + ); + } + + /** + * Builds the routes overview page. + * + * @return array + * A render array as expected by the renderer. + */ + public function routeList() { + $headers = [ + $this->t('Route Name'), + $this->t('Path'), + $this->t('Allowed Methods'), + $this->t('Operations'), + ]; + + $rows = []; + + foreach ($this->routeProvider->getAllRoutes() as $route_name => $route) { + $row['name'] = [ + 'data' => $route_name, + 'filter' => TRUE, + ]; + $row['path'] = [ + 'data' => $route->getPath(), + 'filter' => TRUE, + ]; + $row['methods']['data'] = [ + '#theme' => 'item_list', + '#items' => $route->getMethods(), + '#empty' => $this->t('ANY'), + '#context' => ['list_style' => 'comma-list'], + ]; + + // We cannot resolve routes with dynamic parameters from route path. For + // these routes we pass the route name. + // @see ::routeItem() + if (strpos($route->getPath(), '{') !== FALSE) { + $parameters = ['query' => ['route_name' => $route_name]]; + } + else { + $parameters = ['query' => ['path' => $route->getPath()]]; + } + + $row['operations']['data'] = [ + '#type' => 'operations', + '#links' => [ + 'devel' => [ + 'title' => $this->t('Devel'), + 'url' => Url::fromRoute('devel.route_info.item', [], $parameters), + 'attributes' => [ + 'class' => ['use-ajax'], + 'data-dialog-type' => 'modal', + 'data-dialog-options' => Json::encode([ + 'width' => 700, + 'minHeight' => 500, + ]), + ], + ], + ], + ]; + + $rows[] = $row; + } + + $output['routes'] = [ + '#type' => 'devel_table_filter', + '#filter_label' => $this->t('Search'), + '#filter_placeholder' => $this->t('Enter route name or path'), + '#filter_description' => $this->t('Enter a part of the route name or path to filter by.'), + '#header' => $headers, + '#rows' => $rows, + '#empty' => $this->t('No routes found.'), + '#sticky' => TRUE, + '#attributes' => [ + 'class' => ['devel-route-list'], + ], + ]; + + return $output; + } + + /** + * Returns a render array representation of the route object. + * + * The method tries to resolve the route from the 'path' or the 'route_name' + * query string value if available. If no route is retrieved from the query + * string parameters it fallbacks to the current route. + * + * @param \Symfony\Component\HttpFoundation\Request $request + * The request object. + * @param \Drupal\Core\Routing\RouteMatchInterface $route_match + * The route match. + * + * @return array + * A render array as expected by the renderer. + */ + public function routeDetail(Request $request, RouteMatchInterface $route_match) { + $route = NULL; + + // Get the route object from the path query string if available. + if ($path = $request->query->get('path')) { + try { + $route = $this->router->match($path); + } + catch (\Exception $e) { + $this->messenger()->addWarning($this->t("Unable to load route for url '%url'", ['%url' => $path])); + } + } + + // Get the route object from the route name query string if available and + // the route is not retrieved by path. + if ($route === NULL && $route_name = $request->query->get('route_name')) { + try { + $route = $this->routeProvider->getRouteByName($route_name); + } + catch (\Exception $e) { + $this->messenger()->addWarning($this->t("Unable to load route '%name'", ['%name' => $route_name])); + } + } + + // No route retrieved from path or name specified, get the current route. + if ($route === NULL) { + $route = $route_match->getRouteObject(); + } + + return $this->dumper->exportAsRenderable($route); + } + +} diff --git a/web/modules/contrib/devel/src/Controller/SwitchUserController.php b/web/modules/contrib/devel/src/Controller/SwitchUserController.php new file mode 100644 index 000000000..8f9542ae2 --- /dev/null +++ b/web/modules/contrib/devel/src/Controller/SwitchUserController.php @@ -0,0 +1,120 @@ +account = $account; + $this->userStorage = $user_storage; + $this->moduleHandler = $module_handler; + $this->sessionManager = $session_manager; + $this->session = $session; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('current_user'), + $container->get('entity_type.manager')->getStorage('user'), + $container->get('module_handler'), + $container->get('session_manager'), + $container->get('session') + ); + } + + /** + * Switches to a different user. + * + * We don't call session_save_session() because we really want to change + * users. Usually unsafe! + * + * @param string $name + * The username to switch to, or NULL to log out. + * + * @return \Symfony\Component\HttpFoundation\RedirectResponse + * A redirect response object. + * + * @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException + */ + public function switchUser($name = NULL) { + if (empty($name) || !($account = $this->userStorage->loadByProperties(['name' => $name]))) { + throw new AccessDeniedHttpException(); + } + $account = reset($account); + + // Call logout hooks when switching from original user. + $this->moduleHandler->invokeAll('user_logout', [$this->account]); + + // Regenerate the session ID to prevent against session fixation attacks. + $this->sessionManager->regenerate(); + + // Based off masquarade module as: + // https://www.drupal.org/node/218104 doesn't stick and instead only + // keeps context until redirect. + $this->account->setAccount($account); + $this->session->set('uid', $account->id()); + + // Call all login hooks when switching to masquerading user. + $this->moduleHandler->invokeAll('user_login', [$account]); + + return $this->redirect(''); + } + +} diff --git a/web/modules/contrib/devel/src/DevelDumperBase.php b/web/modules/contrib/devel/src/DevelDumperBase.php new file mode 100644 index 000000000..68458dd5d --- /dev/null +++ b/web/modules/contrib/devel/src/DevelDumperBase.php @@ -0,0 +1,87 @@ +export($input, $name); + } + + /** + * {@inheritdoc} + */ + public function exportAsRenderable($input, $name = NULL) { + return ['#markup' => $this->export($input, $name)]; + } + + /** + * Wrapper for \Drupal\Core\Render\Markup::create(). + * + * @param string $input + * The input string to mark as safe. + * + * @return string + * The unaltered input value. + */ + protected function setSafeMarkup($input) { + return FilteredMarkup::create($input); + } + + /** + * Returns a list of internal functions. + * + * The list returned from this method can be used to exclude internal + * functions from the backtrace output. + * + * @return array + * An array of internal functions. + */ + protected function getInternalFunctions() { + $class_name = get_class($this); + $manager_class_name = DevelDumperManager::class; + + $aliases = [ + [$class_name, 'dump'], + [$class_name, 'export'], + [$manager_class_name, 'dump'], + [$manager_class_name, 'export'], + [$manager_class_name, 'exportAsRenderable'], + [$manager_class_name, 'message'], + [\Drupal\devel\Twig\Extension\Debug::class, 'dump'], + 'dpm', + 'dvm', + 'dsm', + 'dpr', + 'dvr', + 'kpr', + 'dargs', + 'dcp', + 'dfb', + 'dfbt', + 'dpq', + 'kint', + 'ksm', + 'ddebug_backtrace', + 'kdevel_print_object', + 'backtrace_error_handler', + ]; + + return $aliases; + } + +} diff --git a/web/modules/contrib/devel/src/DevelDumperInterface.php b/web/modules/contrib/devel/src/DevelDumperInterface.php new file mode 100644 index 000000000..1236dd0be --- /dev/null +++ b/web/modules/contrib/devel/src/DevelDumperInterface.php @@ -0,0 +1,59 @@ +config = $config_factory->get('devel.settings'); + $this->account = $account; + $this->dumperManager = $dumper_manager; + } + + /** + * Instances a new dumper plugin. + * + * @param string $plugin_id + * (optional) The plugin ID, defaults to NULL. + * + * @return \Drupal\devel\DevelDumperInterface + * Returns the devel dumper plugin instance. + */ + protected function createInstance($plugin_id = NULL) { + if (!$plugin_id || !$this->dumperManager->isPluginSupported($plugin_id)) { + $plugin_id = $this->config->get('devel_dumper'); + } + return $this->dumperManager->createInstance($plugin_id); + } + + /** + * {@inheritdoc} + */ + public function dump($input, $name = NULL, $plugin_id = NULL) { + if ($this->hasAccessToDevelInformation()) { + $this->createInstance($plugin_id)->dump($input, $name); + } + } + + /** + * {@inheritdoc} + */ + public function export($input, $name = NULL, $plugin_id = NULL) { + if ($this->hasAccessToDevelInformation()) { + return $this->createInstance($plugin_id)->export($input, $name); + } + return NULL; + } + + /** + * {@inheritdoc} + */ + public function message($input, $name = NULL, $type = MessengerInterface::TYPE_STATUS, $plugin_id = NULL) { + if ($this->hasAccessToDevelInformation()) { + $output = $this->export($input, $name, $plugin_id); + $this->messenger()->addMessage($output, $type, TRUE); + } + } + + /** + * {@inheritdoc} + */ + public function debug($input, $name = NULL, $plugin_id = NULL) { + $output = $this->createInstance($plugin_id)->export($input, $name) . "\n"; + // The temp directory does vary across multiple simpletest instances. + $file = $this->config->get('debug_logfile'); + if (file_put_contents($file, $output, FILE_APPEND) === FALSE && $this->hasAccessToDevelInformation()) { + $this->messenger()->addError($this->t('Devel was unable to write to %file.', ['%file' => $file])); + return FALSE; + } + } + + /** + * {@inheritdoc} + */ + public function dumpOrExport($input, $name = NULL, $export = TRUE, $plugin_id = NULL) { + if ($this->hasAccessToDevelInformation()) { + $dumper = $this->createInstance($plugin_id); + if ($export) { + return $dumper->export($input, $name); + } + $dumper->dump($input, $name); + } + return NULL; + } + + /** + * {@inheritdoc} + */ + public function exportAsRenderable($input, $name = NULL, $plugin_id = NULL) { + if ($this->hasAccessToDevelInformation()) { + return $this->createInstance($plugin_id)->exportAsRenderable($input, $name); + } + return []; + } + + /** + * Checks whether a user has access to devel information. + * + * @return bool + * TRUE if the user has the permission, FALSE otherwise. + */ + protected function hasAccessToDevelInformation() { + return $this->account && $this->account->hasPermission('access devel information'); + } + +} diff --git a/web/modules/contrib/devel/src/DevelDumperManagerInterface.php b/web/modules/contrib/devel/src/DevelDumperManagerInterface.php new file mode 100644 index 000000000..13259a9f2 --- /dev/null +++ b/web/modules/contrib/devel/src/DevelDumperManagerInterface.php @@ -0,0 +1,104 @@ +setCacheBackend($cache_backend, 'devel_dumper_plugins'); + $this->alterInfo('devel_dumper_info'); + } + + /** + * {@inheritdoc} + */ + public function processDefinition(&$definition, $plugin_id) { + parent::processDefinition($definition, $plugin_id); + + $definition['supported'] = (bool) call_user_func([$definition['class'], 'checkRequirements']); + } + + /** + * {@inheritdoc} + */ + public function isPluginSupported($plugin_id) { + $definition = $this->getDefinition($plugin_id, FALSE); + return $definition && $definition['supported']; + } + + /** + * {@inheritdoc} + */ + public function createInstance($plugin_id, array $configuration = []) { + if (!$this->isPluginSupported($plugin_id)) { + $plugin_id = $this->getFallbackPluginId($plugin_id); + } + return parent::createInstance($plugin_id, $configuration); + } + + /** + * {@inheritdoc} + */ + public function getFallbackPluginId($plugin_id, array $configuration = []) { + return 'default'; + } + +} diff --git a/web/modules/contrib/devel/src/DevelDumperPluginManagerInterface.php b/web/modules/contrib/devel/src/DevelDumperPluginManagerInterface.php new file mode 100644 index 000000000..8f42ed2a3 --- /dev/null +++ b/web/modules/contrib/devel/src/DevelDumperPluginManagerInterface.php @@ -0,0 +1,24 @@ + 'devel_table_filter', + * '#filter_label' => $this->t('Search'), + * '#filter_placeholder' => $this->t('Enter element name.'), + * '#filter_description' => $this->t('Enter a part of name to filter by.'), + * '#header' => $headers, + * '#rows' => $rows, + * '#empty' => $this->t('No element found.'), + * ]; + * @endcode + * + * @RenderElement("devel_table_filter") + */ +class ClientSideFilterTable extends RenderElement { + + /** + * {@inheritdoc} + */ + public function getInfo() { + $class = get_class($this); + return [ + '#filter_label' => $this->t('Search'), + '#filter_placeholder' => $this->t('Search'), + '#filter_description' => $this->t('Search'), + '#header' => [], + '#rows' => [], + '#empty' => '', + '#sticky' => FALSE, + '#responsive' => TRUE, + '#attributes' => [], + '#pre_render' => [ + [$class, 'preRenderTable'], + ], + ]; + } + + /** + * Pre-render callback: Assemble render array for the filterable table. + * + * @param array $element + * An associative array containing the properties of the element. + * + * @return array + * The $element with prepared render array ready for rendering. + */ + public static function preRenderTable(array $element) { + $build['#attached']['library'][] = 'devel/devel-table-filter'; + $identifier = Html::getUniqueId('js-devel-table-filter'); + + $build['filters'] = [ + '#type' => 'container', + '#weight' => -1, + '#attributes' => [ + 'class' => ['table-filter', 'js-show'], + ], + ]; + + $build['filters']['name'] = [ + '#type' => 'search', + '#size' => 30, + '#title' => $element['#filter_label'], + '#placeholder' => $element['#filter_placeholder'], + '#attributes' => [ + 'class' => ['table-filter-text'], + 'data-table' => ".$identifier", + 'autocomplete' => 'off', + 'title' => $element['#filter_description'], + ], + ]; + + foreach ($element['#rows'] as &$row) { + foreach ($row as &$cell) { + if (isset($cell['data']) && !empty($cell['filter'])) { + $cell['class'][] = 'table-filter-text-source'; + } + } + } + + $build['table'] = [ + '#type' => 'table', + '#header' => $element['#header'], + '#rows' => $element['#rows'], + '#empty' => $element['#empty'], + '#sticky' => $element['#sticky'], + '#responsive' => $element['#responsive'], + '#attributes' => $element['#attributes'], + ]; + + $build['table']['#attributes']['class'][] = $identifier; + $build['table']['#attributes']['class'][] = 'devel-table-filter'; + + return $build; + } + +} diff --git a/web/modules/contrib/devel/src/EntityTypeInfo.php b/web/modules/contrib/devel/src/EntityTypeInfo.php new file mode 100644 index 000000000..a72417a40 --- /dev/null +++ b/web/modules/contrib/devel/src/EntityTypeInfo.php @@ -0,0 +1,103 @@ +currentUser = $current_user; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('current_user') + ); + } + + /** + * Adds devel links to appropriate entity types. + * + * This is an alter hook bridge. + * + * @param \Drupal\Core\Entity\EntityTypeInterface[] $entity_types + * The master entity type list to alter. + * + * @see hook_entity_type_alter() + */ + public function entityTypeAlter(array &$entity_types) { + foreach ($entity_types as $entity_type_id => $entity_type) { + if (($entity_type->getFormClass('default') || $entity_type->getFormClass('edit')) && $entity_type->hasLinkTemplate('edit-form')) { + $entity_type->setLinkTemplate('devel-load', "/devel/$entity_type_id/{{$entity_type_id}}"); + } + if ($entity_type->hasViewBuilderClass() && $entity_type->hasLinkTemplate('canonical')) { + $entity_type->setLinkTemplate('devel-render', "/devel/$entity_type_id/{{$entity_type_id}}/render"); + } + if ($entity_type->hasLinkTemplate('devel-render') || $entity_type->hasLinkTemplate('devel-load')) { + $entity_type->setLinkTemplate('devel-definition', "/devel/$entity_type_id/{{$entity_type_id}}/definition"); + } + } + } + + /** + * Adds devel operations on entity that supports it. + * + * @param \Drupal\Core\Entity\EntityInterface $entity + * The entity on which to define an operation. + * + * @return array + * An array of operation definitions. + * + * @see hook_entity_operation() + */ + public function entityOperation(EntityInterface $entity) { + $operations = []; + if ($this->currentUser->hasPermission('access devel information')) { + if ($entity->hasLinkTemplate('devel-load')) { + $operations['devel'] = [ + 'title' => $this->t('Devel'), + 'weight' => 100, + 'url' => $entity->toUrl('devel-load'), + ]; + } + elseif ($entity->hasLinkTemplate('devel-render')) { + $operations['devel'] = [ + 'title' => $this->t('Devel'), + 'weight' => 100, + 'url' => $entity->toUrl('devel-render'), + ]; + } + } + return $operations; + } + +} diff --git a/web/modules/contrib/devel/src/EventSubscriber/ErrorHandlerSubscriber.php b/web/modules/contrib/devel/src/EventSubscriber/ErrorHandlerSubscriber.php new file mode 100644 index 000000000..ee5ad21ba --- /dev/null +++ b/web/modules/contrib/devel/src/EventSubscriber/ErrorHandlerSubscriber.php @@ -0,0 +1,57 @@ +account = $account; + } + + /** + * Register devel error handler. + * + * @param \Symfony\Component\EventDispatcher\Event $event + * The event to process. + */ + public function registerErrorHandler(Event $event = NULL) { + if ($this->account && $this->account->hasPermission('access devel information')) { + devel_set_handler(devel_get_handlers()); + } + } + + /** + * {@inheritdoc} + */ + public static function getSubscribedEvents() { + // Runs as soon as possible in the request but after + // AuthenticationSubscriber (priority 300) because you need to access to + // the current user for determine whether register the devel error handler + // or not. + $events[KernelEvents::REQUEST][] = ['registerErrorHandler', 256]; + + return $events; + } + +} diff --git a/web/modules/contrib/devel/src/EventSubscriber/ThemeInfoRebuildSubscriber.php b/web/modules/contrib/devel/src/EventSubscriber/ThemeInfoRebuildSubscriber.php new file mode 100644 index 000000000..9b833ecc0 --- /dev/null +++ b/web/modules/contrib/devel/src/EventSubscriber/ThemeInfoRebuildSubscriber.php @@ -0,0 +1,119 @@ +config = $config->get('devel.settings'); + $this->account = $account; + $this->themeHandler = $theme_handler; + } + + /** + * Forces the system to rebuild the theme registry. + * + * @param \Symfony\Component\HttpKernel\Event\GetResponseEvent $event + * The event to process. + */ + public function rebuildThemeInfo(GetResponseEvent $event) { + if ($this->config->get('rebuild_theme')) { + // Update the theme registry. + drupal_theme_rebuild(); + // Refresh theme data. + $this->themeHandler->refreshInfo(); + // Resets the internal state of the theme handler and clear the 'system + // list' cache; this allow to properly register, if needed, PSR-4 + // namespaces for theme extensions after refreshing the info data. + $this->themeHandler->reset(); + // Notify the user that the theme info are rebuilt on every request. + $this->triggerWarningIfNeeded($event->getRequest()); + } + } + + /** + * Notifies the user that the theme info are rebuilt on every request. + * + * The warning message is shown only to users with adequate permissions and + * only once per session. + * + * @param \Symfony\Component\HttpFoundation\Request $request + * The request. + */ + protected function triggerWarningIfNeeded(Request $request) { + if ($this->account && $this->account->hasPermission('access devel information')) { + $session = $request->getSession(); + if ($session && !$session->has($this->notificationFlag)) { + $session->set($this->notificationFlag, TRUE); + $message = $this->t('The theme information is being rebuilt on every request. Remember to turn off this feature on production websites.', [':url' => Url::fromRoute('devel.admin_settings')->toString()]); + $this->messenger()->addWarning($message); + + } + } + } + + /** + * {@inheritdoc} + */ + public static function getSubscribedEvents() { + // Set high priority value to start as early as possible. + $events[KernelEvents::REQUEST][] = ['rebuildThemeInfo', 256]; + return $events; + } + +} diff --git a/web/modules/contrib/devel/src/Form/ConfigEditor.php b/web/modules/contrib/devel/src/Form/ConfigEditor.php new file mode 100644 index 000000000..efefe6b26 --- /dev/null +++ b/web/modules/contrib/devel/src/Form/ConfigEditor.php @@ -0,0 +1,155 @@ +config($config_name); + + if ($config === FALSE || $config->isNew()) { + $this->messenger()->addError($this->t('Config @name does not exist in the system.', ['@name' => $config_name])); + return; + } + + $data = $config->getOriginal(); + + if (empty($data)) { + $this->messenger()->addWarning($this->t('Config @name exists but has no data.', ['@name' => $config_name])); + return; + } + + try { + $output = Yaml::encode($data); + } + catch (InvalidDataTypeException $e) { + $this->messenger()->addError($this->t('Invalid data detected for @name : %error', ['@name' => $config_name, '%error' => $e->getMessage()])); + return; + } + + $form['current'] = [ + '#type' => 'details', + '#title' => $this->t('Current value for %variable', ['%variable' => $config_name]), + '#attributes' => ['class' => ['container-inline']], + ]; + $form['current']['value'] = [ + '#type' => 'item', + // phpcs:ignore Drupal.Functions.DiscouragedFunctions + '#markup' => dpr($output, TRUE), + ]; + + $form['name'] = [ + '#type' => 'value', + '#value' => $config_name, + ]; + $form['new'] = [ + '#type' => 'textarea', + '#title' => $this->t('New value'), + '#default_value' => $output, + '#rows' => 24, + '#required' => TRUE, + ]; + + $form['actions'] = ['#type' => 'actions']; + $form['actions']['submit'] = [ + '#type' => 'submit', + '#value' => $this->t('Save'), + ]; + $form['actions']['cancel'] = [ + '#type' => 'link', + '#title' => $this->t('Cancel'), + '#url' => $this->buildCancelLinkUrl(), + ]; + $form['actions']['delete'] = [ + '#type' => 'link', + '#title' => $this->t('Delete'), + '#url' => Url::fromRoute('devel.config_delete', ['config_name' => $config_name]), + '#attributes' => [ + 'class' => ['button', 'button--danger'], + ], + ]; + + return $form; + } + + /** + * {@inheritdoc} + */ + public function validateForm(array &$form, FormStateInterface $form_state) { + $value = $form_state->getValue('new'); + // Try to parse the new provided value. + try { + $parsed_value = Yaml::decode($value); + // Config::setData needs array for the new configuration and + // a simple string is valid YAML for any reason. + if (is_array($parsed_value)) { + $form_state->setValue('parsed_value', $parsed_value); + } + else { + $form_state->setErrorByName('new', $this->t('Invalid input')); + } + } + catch (InvalidDataTypeException $e) { + $form_state->setErrorByName('new', $this->t('Invalid input: %error', ['%error' => $e->getMessage()])); + } + } + + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, FormStateInterface $form_state) { + $values = $form_state->getValues(); + try { + $this->configFactory()->getEditable($values['name'])->setData($values['parsed_value'])->save(); + $this->messenger()->addMessage($this->t('Configuration variable %variable was successfully saved.', ['%variable' => $values['name']])); + $this->logger('devel')->info('Configuration variable %variable was successfully saved.', ['%variable' => $values['name']]); + + $form_state->setRedirectUrl(Url::fromRoute('devel.configs_list')); + } + catch (\Exception $e) { + $this->messenger()->addError($e->getMessage()); + $this->logger('devel')->error('Error saving configuration variable %variable : %error.', ['%variable' => $values['name'], '%error' => $e->getMessage()]); + } + } + + /** + * Builds the cancel link url for the form. + * + * @return \Drupal\Core\Url + * Cancel url + */ + private function buildCancelLinkUrl() { + $query = $this->getRequest()->query; + + if ($query->has('destination')) { + $options = UrlHelper::parse($query->get('destination')); + $url = Url::fromUserInput('/' . ltrim($options['path'], '/'), $options); + } + else { + $url = Url::fromRoute('devel.configs_list'); + } + + return $url; + } + +} diff --git a/web/modules/contrib/devel/src/Form/ConfigsList.php b/web/modules/contrib/devel/src/Form/ConfigsList.php new file mode 100644 index 000000000..e460c464e --- /dev/null +++ b/web/modules/contrib/devel/src/Form/ConfigsList.php @@ -0,0 +1,86 @@ + 'details', + '#title' => $this->t('Filter variables'), + '#attributes' => ['class' => ['container-inline']], + '#open' => isset($filter) && trim($filter) != '', + ]; + $form['filter']['name'] = [ + '#type' => 'textfield', + '#title' => $this->t('Variable name'), + '#title_display' => 'invisible', + '#default_value' => $filter, + ]; + $form['filter']['actions'] = ['#type' => 'actions']; + $form['filter']['actions']['show'] = [ + '#type' => 'submit', + '#value' => $this->t('Filter'), + ]; + + $header = [ + 'name' => ['data' => $this->t('Name')], + 'edit' => ['data' => $this->t('Operations')], + ]; + + $rows = []; + + $destination = $this->getDestinationArray(); + + // List all the variables filtered if any filter was provided. + $names = $this->configFactory()->listAll($filter); + + foreach ($names as $config_name) { + $operations['edit'] = [ + 'title' => $this->t('Edit'), + 'url' => Url::fromRoute('devel.config_edit', ['config_name' => $config_name]), + 'query' => $destination, + ]; + $rows[] = [ + 'name' => $config_name, + 'operation' => ['data' => ['#type' => 'operations', '#links' => $operations]], + ]; + } + + $form['variables'] = [ + '#type' => 'table', + '#header' => $header, + '#rows' => $rows, + '#empty' => $this->t('No variables found'), + ]; + + return $form; + } + + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, FormStateInterface $form_state) { + $filter = $form_state->getValue('name'); + $form_state->setRedirectUrl(Url::FromRoute('devel.configs_list', ['filter' => Html::escape($filter)])); + } + +} diff --git a/web/modules/contrib/devel/src/Form/DevelReinstall.php b/web/modules/contrib/devel/src/Form/DevelReinstall.php new file mode 100644 index 000000000..32f89b46a --- /dev/null +++ b/web/modules/contrib/devel/src/Form/DevelReinstall.php @@ -0,0 +1,166 @@ +moduleInstaller = $module_installer; + $this->moduleExtensionList = $extension_list_module; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('module_installer'), + $container->get('extension.list.module') + ); + } + + /** + * {@inheritdoc} + */ + public function getFormId() { + return 'devel_reinstall_form'; + } + + /** + * {@inheritdoc} + */ + public function buildForm(array $form, FormStateInterface $form_state) { + // Get a list of all available modules. + $modules = $this->moduleExtensionList->reset()->getList(); + + $uninstallable = array_filter($modules, function ($module) use ($modules) { + return empty($modules[$module->getName()]->info['required']) && drupal_get_installed_schema_version($module->getName()) > SCHEMA_UNINSTALLED && $module->getName() !== 'devel'; + }); + + $form['filters'] = [ + '#type' => 'container', + '#attributes' => [ + 'class' => ['table-filter', 'js-show'], + ], + ]; + $form['filters']['text'] = [ + '#type' => 'search', + '#title' => $this->t('Search'), + '#size' => 30, + '#placeholder' => $this->t('Enter module name'), + '#attributes' => [ + 'class' => ['table-filter-text'], + 'data-table' => '#devel-reinstall-form', + 'autocomplete' => 'off', + 'title' => $this->t('Enter a part of the module name or description to filter by.'), + ], + ]; + + // Only build the rest of the form if there are any modules available to + // uninstall. + if (empty($uninstallable)) { + return $form; + } + + $header = [ + 'name' => $this->t('Name'), + 'description' => $this->t('Description'), + ]; + + $rows = []; + + foreach ($uninstallable as $module) { + $name = $module->info['name'] ?: $module->getName(); + + $rows[$module->getName()] = [ + 'name' => [ + 'data' => [ + '#type' => 'inline_template', + '#template' => '', + '#context' => ['module_name' => $name], + ], + ], + 'description' => [ + 'data' => $module->info['description'], + 'class' => ['description'], + ], + ]; + } + + $form['reinstall'] = [ + '#type' => 'tableselect', + '#header' => $header, + '#options' => $rows, + '#js_select' => FALSE, + '#empty' => $this->t('No modules are available to uninstall.'), + ]; + + $form['#attached']['library'][] = 'system/drupal.system.modules'; + + $form['actions'] = ['#type' => 'actions']; + $form['actions']['submit'] = [ + '#type' => 'submit', + '#value' => $this->t('Reinstall'), + ]; + + return $form; + } + + /** + * {@inheritdoc} + */ + public function validateForm(array &$form, FormStateInterface $form_state) { + // Form submitted, but no modules selected. + if (!array_filter($form_state->getValue('reinstall'))) { + $form_state->setErrorByName('reinstall', $this->t('No modules selected.')); + } + } + + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, FormStateInterface $form_state) { + try { + $modules = $form_state->getValue('reinstall'); + $reinstall = array_keys(array_filter($modules)); + $this->moduleInstaller->uninstall($reinstall, FALSE); + $this->moduleInstaller->install($reinstall, FALSE); + $this->messenger()->addMessage($this->t('Uninstalled and installed: %names.', ['%names' => implode(', ', $reinstall)])); + } + catch (\Exception $e) { + $this->messenger()->addError($this->t('Unable to reinstall modules. Error: %error.', ['%error' => $e->getMessage()])); + } + } + +} diff --git a/web/modules/contrib/devel/src/Form/RouterRebuildConfirmForm.php b/web/modules/contrib/devel/src/Form/RouterRebuildConfirmForm.php new file mode 100644 index 000000000..c6e7383ef --- /dev/null +++ b/web/modules/contrib/devel/src/Form/RouterRebuildConfirmForm.php @@ -0,0 +1,86 @@ +routeBuilder = $route_builder; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('router.builder') + ); + } + + /** + * {@inheritdoc} + */ + public function getFormId() { + return 'devel_menu_rebuild'; + } + + /** + * {@inheritdoc} + */ + public function getQuestion() { + return $this->t('Are you sure you want to rebuild the router?'); + } + + /** + * {@inheritdoc} + */ + public function getCancelUrl() { + return new Url(''); + } + + /** + * {@inheritdoc} + */ + public function getDescription() { + return $this->t('Rebuilds the routes information gathering all routing data from .routing.yml files and from classes which subscribe to the route build events. This action cannot be undone.'); + } + + /** + * {@inheritdoc} + */ + public function getConfirmText() { + return $this->t('Rebuild'); + } + + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, FormStateInterface $form_state) { + $this->routeBuilder->rebuild(); + $this->messenger()->addMessage($this->t('The router has been rebuilt.')); + $form_state->setRedirect(''); + } + +} diff --git a/web/modules/contrib/devel/src/Form/SettingsForm.php b/web/modules/contrib/devel/src/Form/SettingsForm.php new file mode 100644 index 000000000..3c44427e4 --- /dev/null +++ b/web/modules/contrib/devel/src/Form/SettingsForm.php @@ -0,0 +1,225 @@ +dumperManager = $devel_dumper_manager; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('plugin.manager.devel_dumper') + ); + } + + /** + * {@inheritdoc} + */ + public function getFormId() { + return 'devel_admin_settings_form'; + } + + /** + * {@inheritdoc} + */ + protected function getEditableConfigNames() { + return [ + 'devel.settings', + ]; + } + + /** + * {@inheritdoc} + */ + public function buildForm(array $form, FormStateInterface $form_state, Request $request = NULL) { + $current_url = Url::createFromRequest($request); + $devel_config = $this->config('devel.settings'); + + $form['page_alter'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Display $page array'), + '#default_value' => $devel_config->get('page_alter'), + '#description' => $this->t('Display $page array from hook_page_attachments_alter() in the messages area of each page.'), + ]; + $form['raw_names'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Display machine names of permissions and modules'), + '#default_value' => $devel_config->get('raw_names'), + '#description' => $this->t('Display the language-independent machine names of the permissions in mouse-over hints on the Permissions page and the module base file names on the Permissions and Modules pages.', [ + ':permissions_url' => Url::fromRoute('user.admin_permissions')->toString(), + ':modules_url' => Url::fromRoute('system.modules_list')->toString(), + ]), + ]; + $form['rebuild_theme'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Rebuild the theme registry on every page load'), + '#description' => $this->t('New templates, theme overrides, and changes to the theme.info.yml need the theme registry to be rebuilt in order to appear on the site.'), + '#default_value' => $devel_config->get('rebuild_theme'), + ]; + + $error_handlers = devel_get_handlers(); + $form['error_handlers'] = [ + '#type' => 'select', + '#title' => $this->t('Error handlers'), + '#options' => [ + DEVEL_ERROR_HANDLER_NONE => $this->t('None'), + DEVEL_ERROR_HANDLER_STANDARD => $this->t('Standard Drupal'), + DEVEL_ERROR_HANDLER_BACKTRACE_DPM => $this->t('Backtrace in the message area'), + DEVEL_ERROR_HANDLER_BACKTRACE_KINT => $this->t('Backtrace above the rendered page'), + ], + '#multiple' => TRUE, + '#default_value' => empty($error_handlers) ? DEVEL_ERROR_HANDLER_NONE : $error_handlers, + '#description' => [ + [ + '#markup' => $this->t('Select the error handler(s) to use, in case you choose to show errors on screen.', [':choose' => Url::fromRoute('system.logging_settings')->toString()]), + ], + [ + '#theme' => 'item_list', + '#items' => [ + $this->t('None is a good option when stepping through the site in your debugger.'), + $this->t('Standard Drupal does not display all the information that is often needed to resolve an issue.'), + $this->t('Backtrace displays nice debug information when any type of error is noticed, but only to users with the %perm permission.', ['%perm' => $this->t('Access developer information')]), + ], + ], + [ + '#markup' => $this->t('Depending on the situation, the theme, the size of the call stack and the arguments, etc., some handlers may not display their messages, or display them on the subsequent page. Select Standard Drupal and Backtrace above the rendered page to maximize your chances of not missing any messages.') . '
' . + $this->t('Demonstrate the current error handler(s):') . ' ' . + Link::fromTextAndUrl('notice', $current_url->setOption('query', ['demo' => 'notice']))->toString() . ', ' . + Link::fromTextAndUrl('notice+warning', $current_url->setOption('query', ['demo' => 'warning']))->toString() . ', ' . + Link::fromTextAndUrl('notice+warning+error', $current_url->setOption('query', ['demo' => 'error']))->toString() . ' (' . + $this->t('The presentation of the @error is determined by PHP.', ['@error' => 'error']) . ')', + ], + ], + ]; + + $form['error_handlers']['#size'] = count($form['error_handlers']['#options']); + if ($request->query->has('demo')) { + if ($request->getMethod() == 'GET') { + $this->demonstrateErrorHandlers($request->query->get('demo')); + } + $request->query->remove('demo'); + } + + $dumper = $devel_config->get('devel_dumper'); + $default = $this->dumperManager->isPluginSupported($dumper) ? $dumper : $this->dumperManager->getFallbackPluginId(NULL); + + $form['dumper'] = [ + '#type' => 'radios', + '#title' => $this->t('Variables Dumper'), + '#options' => [], + '#default_value' => $default, + '#description' => $this->t('Select the debugging tool used for formatting and displaying the variables inspected through the debug functions of Devel. NOTE: Some of these plugins require external libraries for to be enabled. Learn how install external libraries with Composer.', [ + ':url' => 'https://www.drupal.org/node/2404989', + ]), + ]; + + foreach ($this->dumperManager->getDefinitions() as $id => $definition) { + $form['dumper']['#options'][$id] = $definition['label']; + + $supported = $this->dumperManager->isPluginSupported($id); + $form['dumper'][$id]['#disabled'] = !$supported; + + $form['dumper'][$id]['#description'] = [ + '#type' => 'inline_template', + '#template' => '{{ description }}{% if not supported %}
{% trans %}Not available. You may need to install external dependencies for use this plugin.{% endtrans %}
{% endif %}', + '#context' => [ + 'description' => $definition['description'], + 'supported' => $supported, + ], + ]; + } + + // Allow custom debug filename for use in DevelDumperManager::debug() + $default_file = $devel_config->get('debug_logfile') ?: 'temporary://drupal_debug.txt'; + $form['debug_logfile'] = [ + '#type' => 'textfield', + '#title' => $this->t('Debug Log File'), + '#description' => $this->t('This is the log file that Devel functions such as ddm() write to. Use temporary:// to represent your systems temporary directory. Save with a blank filename to revert to the default.'), + '#default_value' => $default_file, + ]; + + // Specify whether debug file should have
 tags around each $dump,
+    // for use in Plugin\Devel\Dumper\DoctrineDebug::export()
+    $form['debug_pre'] = [
+      '#type' => 'checkbox',
+      '#title' => $this->t('Wrap debug in <pre> tags'),
+      '#default_value' => $devel_config->get('debug_pre'),
+      '#description' => $this->t('You may want the debug output wrapped in <pre> tags, depending on your debug file format and how it is displayed.'),
+    ];
+
+    return parent::buildForm($form, $form_state);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function submitForm(array &$form, FormStateInterface $form_state) {
+    $values = $form_state->getValues();
+    $this->config('devel.settings')
+      ->set('page_alter', $values['page_alter'])
+      ->set('raw_names', $values['raw_names'])
+      ->set('error_handlers', $values['error_handlers'])
+      ->set('rebuild_theme', $values['rebuild_theme'])
+      ->set('devel_dumper', $values['dumper'])
+      ->set('debug_logfile', $values['debug_logfile'] ?: 'temporary://drupal_debug.txt')
+      ->set('debug_pre', $values['debug_pre'])
+      ->save();
+
+    parent::submitForm($form, $form_state);
+  }
+
+  /**
+   * Demonstrates the capabilities of the error handler.
+   *
+   * @param string $severity
+   *   The severity level for which demonstrate the error handler capabilities.
+   */
+  protected function demonstrateErrorHandlers($severity) {
+    switch ($severity) {
+      case 'notice':
+        trigger_error('This is an example notice', E_USER_NOTICE);
+        break;
+
+      case 'warning':
+        trigger_error('This is an example notice', E_USER_NOTICE);
+        trigger_error('This is an example warning', E_USER_WARNING);
+        break;
+
+      case 'error':
+        trigger_error('This is an example notice', E_USER_NOTICE);
+        trigger_error('This is an example warning', E_USER_WARNING);
+        trigger_error('This is an example error', E_USER_ERROR);
+        break;
+    }
+  }
+
+}
diff --git a/web/modules/contrib/devel/src/Form/SwitchUserForm.php b/web/modules/contrib/devel/src/Form/SwitchUserForm.php
new file mode 100644
index 000000000..7b9c44d7a
--- /dev/null
+++ b/web/modules/contrib/devel/src/Form/SwitchUserForm.php
@@ -0,0 +1,110 @@
+csrfToken = $csrf_token_generator;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static(
+      $container->get('csrf_token')
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFormId() {
+    return 'devel_switchuser_form';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildForm(array $form, FormStateInterface $form_state) {
+    $form['autocomplete'] = [
+      '#type' => 'container',
+      '#attributes' => [
+        'class' => ['container-inline'],
+      ],
+    ];
+    $form['autocomplete']['userid'] = [
+      '#type' => 'entity_autocomplete',
+      '#title' => $this->t('Username'),
+      '#placeholder' => $this->t('Enter username'),
+      '#target_type' => 'user',
+      '#selection_settings' => [
+        'include_anonymous' => FALSE,
+      ],
+      '#process_default_value' => FALSE,
+      '#maxlength' => UserInterface::USERNAME_MAX_LENGTH,
+      '#title_display' => 'invisible',
+      '#required' => TRUE,
+      '#size' => '28',
+    ];
+
+    $form['autocomplete']['actions'] = ['#type' => 'actions'];
+    $form['autocomplete']['actions']['submit'] = [
+      '#type' => 'submit',
+      '#value' => $this->t('Switch'),
+    ];
+
+    return $form;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function validateForm(array &$form, FormStateInterface $form_state) {
+    if (!$account = User::load($form_state->getValue('userid'))) {
+      $form_state->setErrorByName('userid', $this->t('Username not found'));
+    }
+    else {
+      $form_state->setValue('username', $account->getAccountName());
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function submitForm(array &$form, FormStateInterface $form_state) {
+    // We cannot rely on automatic token creation, since the csrf seed changes
+    // after the redirect and the generated token is not more valid.
+    // TODO find another way to do this.
+    $url = Url::fromRoute('devel.switch', ['name' => $form_state->getValue('username')]);
+    $url->setOption('query', ['token' => $this->csrfToken->get($url->getInternalPath())]);
+
+    $form_state->setRedirectUrl($url);
+  }
+
+}
diff --git a/web/modules/contrib/devel/src/Form/SystemStateEdit.php b/web/modules/contrib/devel/src/Form/SystemStateEdit.php
new file mode 100644
index 000000000..8899885c3
--- /dev/null
+++ b/web/modules/contrib/devel/src/Form/SystemStateEdit.php
@@ -0,0 +1,190 @@
+state = $state;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static(
+      $container->get('state')
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFormId() {
+    return 'devel_state_system_edit_form';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildForm(array $form, FormStateInterface $form_state, $state_name = '') {
+    // Get the old value.
+    $old_value = $this->state->get($state_name);
+
+    if (!isset($old_value)) {
+      $this->messenger()->addWarning($this->t('State @name does not exist in the system.', ['@name' => $state_name]));
+      return;
+    }
+
+    // Only simple structures are allowed to be edited.
+    $disabled = !$this->checkObject($old_value);
+
+    if ($disabled) {
+      $this->messenger()->addWarning($this->t('Only simple structures are allowed to be edited. State @name contains objects.', ['@name' => $state_name]));
+
+    }
+
+    // First we will show the user the content of the variable about to be edited.
+    $form['value'] = [
+      '#type' => 'item',
+      '#title' => $this->t('Current value for %name', ['%name' => $state_name]),
+      // phpcs:ignore Drupal.Functions.DiscouragedFunctions
+      '#markup' => kpr($old_value, TRUE),
+    ];
+
+    $transport = 'plain';
+
+    if (!$disabled && is_array($old_value)) {
+      try {
+        $old_value = Yaml::encode($old_value);
+        $transport = 'yaml';
+      }
+      catch (InvalidDataTypeException $e) {
+        $this->messenger()->addError($this->t('Invalid data detected for @name : %error', ['@name' => $state_name, '%error' => $e->getMessage()]));
+        return;
+      }
+    }
+
+    // Store in the form the name of the state variable.
+    $form['state_name'] = [
+      '#type' => 'value',
+      '#value' => $state_name,
+    ];
+    // Set the transport format for the new value. Values:
+    //  - plain
+    //  - yaml.
+    $form['transport'] = [
+      '#type' => 'value',
+      '#value' => $transport,
+    ];
+
+    $form['new_value'] = [
+      '#type' => 'textarea',
+      '#title' => $this->t('New value'),
+      '#default_value' => $disabled ? '' : $old_value,
+      '#disabled' => $disabled,
+      '#rows' => 15,
+    ];
+
+    $form['actions'] = ['#type' => 'actions'];
+    $form['actions']['submit'] = [
+      '#type' => 'submit',
+      '#value' => $this->t('Save'),
+      '#disabled' => $disabled,
+    ];
+    $form['actions']['cancel'] = [
+      '#type' => 'link',
+      '#title' => $this->t('Cancel'),
+      '#url' => Url::fromRoute('devel.state_system_page'),
+    ];
+
+    return $form;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function validateForm(array &$form, FormStateInterface $form_state) {
+    $values = $form_state->getValues();
+
+    if ($values['transport'] == 'yaml') {
+      // Try to parse the new provided value.
+      try {
+        $parsed_value = Yaml::decode($values['new_value']);
+        $form_state->setValue('parsed_value', $parsed_value);
+      }
+      catch (InvalidDataTypeException $e) {
+        $form_state->setErrorByName('new_value', $this->t('Invalid input: %error', ['%error' => $e->getMessage()]));
+      }
+    }
+    else {
+      $form_state->setValue('parsed_value', $values['new_value']);
+    }
+
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function submitForm(array &$form, FormStateInterface $form_state) {
+    // Save the state.
+    $values = $form_state->getValues();
+    $this->state->set($values['state_name'], $values['parsed_value']);
+
+    $form_state->setRedirectUrl(Url::fromRoute('devel.state_system_page'));
+    $this->messenger()->addMessage($this->t('Variable %variable was successfully edited.', ['%variable' => $values['state_name']]));
+    $this->logger('devel')->info('Variable %variable was successfully edited.', ['%variable' => $values['state_name']]);
+  }
+
+  /**
+   * Helper function to determine if a variable is or contains an object.
+   *
+   * @param $data
+   *   Input data to check
+   *
+   * @return bool
+   *   TRUE if the variable is not an object and does not contain one.
+   */
+  protected function checkObject($data) {
+    if (is_object($data)) {
+      return FALSE;
+    }
+    if (is_array($data)) {
+      // If the current object is an array, then check recursively.
+      foreach ($data as $value) {
+        // If there is an object the whole container is "contaminated".
+        if (!$this->checkObject($value)) {
+          return FALSE;
+        }
+      }
+    }
+
+    // All checks pass.
+    return TRUE;
+  }
+
+}
diff --git a/web/modules/contrib/devel/src/Form/ToolbarSettingsForm.php b/web/modules/contrib/devel/src/Form/ToolbarSettingsForm.php
new file mode 100644
index 000000000..d0e09fc7e
--- /dev/null
+++ b/web/modules/contrib/devel/src/Form/ToolbarSettingsForm.php
@@ -0,0 +1,118 @@
+menuLinkTree = $menu_link_tree;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static(
+      $container->get('config.factory'),
+      $container->get('menu.link_tree')
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFormId() {
+    return 'devel_toolbar_settings_form';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getEditableConfigNames() {
+    return [
+      'devel.toolbar.settings',
+    ];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildForm(array $form, FormStateInterface $form_state) {
+    $config = $this->config('devel.toolbar.settings');
+
+    $form['toolbar_items'] = [
+      '#type' => 'checkboxes',
+      '#title' => $this->t('Menu items always visible'),
+      '#options' => $this->getLinkLabels(),
+      '#default_value' => $config->get('toolbar_items') ?: [],
+      '#required' => TRUE,
+      '#description' => $this->t('Select the menu items always visible in devel toolbar tray. All the items not selected in this list will be visible only when the toolbar orientation is vertical.'),
+    ];
+
+    return parent::buildForm($form, $form_state);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function submitForm(array &$form, FormStateInterface $form_state) {
+    $values = $form_state->getValues();
+    $toolbar_items = array_keys(array_filter($values['toolbar_items']));
+
+    $this->config('devel.toolbar.settings')
+      ->set('toolbar_items', $toolbar_items)
+      ->save();
+
+    parent::submitForm($form, $form_state);
+  }
+
+  /**
+   * Provides an array of available menu items.
+   *
+   * @return array
+   *   Associative array of devel menu item labels keyed by plugin ID.
+   */
+  protected function getLinkLabels() {
+    $options = [];
+
+    $parameters = new MenuTreeParameters();
+    $parameters->onlyEnabledLinks()->setTopLevelOnly();
+    $tree = $this->menuLinkTree->load('devel', $parameters);
+
+    foreach ($tree as $element) {
+      $link = $element->link;
+      $options[$link->getPluginId()] = $link->getTitle();
+    }
+
+    asort($options);
+
+    return $options;
+  }
+
+}
diff --git a/web/modules/contrib/devel/src/Plugin/Block/SwitchUserBlock.php b/web/modules/contrib/devel/src/Plugin/Block/SwitchUserBlock.php
new file mode 100644
index 000000000..d85222ce8
--- /dev/null
+++ b/web/modules/contrib/devel/src/Plugin/Block/SwitchUserBlock.php
@@ -0,0 +1,298 @@
+formBuilder = $form_builder;
+    $this->currentUser = $current_user;
+    $this->userStorage = $user_storage;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
+    return new static(
+      $configuration,
+      $plugin_id,
+      $plugin_definition,
+      $container->get('current_user'),
+      $container->get('entity_type.manager')->getStorage('user'),
+      $container->get('form_builder')
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function defaultConfiguration() {
+    return [
+      'list_size' => 12,
+      'include_anon' => FALSE,
+      'show_form' => TRUE,
+    ];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function blockAccess(AccountInterface $account) {
+    return AccessResult::allowedIfHasPermission($account, 'switch users');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function blockForm($form, FormStateInterface $form_state) {
+    $anonymous = new AnonymousUserSession();
+    $form['list_size'] = [
+      '#type' => 'number',
+      '#title' => $this->t('Number of users to display in the list'),
+      '#default_value' => $this->configuration['list_size'],
+      '#min' => 1,
+      '#max' => 50,
+    ];
+    $form['include_anon'] = [
+      '#type' => 'checkbox',
+      '#title' => $this->t('Include %anonymous', ['%anonymous' => $anonymous->getDisplayName()]),
+      '#default_value' => $this->configuration['include_anon'],
+    ];
+    $form['show_form'] = [
+      '#type' => 'checkbox',
+      '#title' => $this->t('Allow entering any user name'),
+      '#default_value' => $this->configuration['show_form'],
+    ];
+
+    return $form;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function blockSubmit($form, FormStateInterface $form_state) {
+    $this->configuration['list_size'] = $form_state->getValue('list_size');
+    $this->configuration['include_anon'] = $form_state->getValue('include_anon');
+    $this->configuration['show_form'] = $form_state->getValue('show_form');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getCacheMaxAge() {
+    return 0;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function build() {
+    $build = [];
+    if ($accounts = $this->getUsers()) {
+      $build['devel_links'] = $this->buildUserList($accounts);
+
+      if ($this->configuration['show_form']) {
+        $build['devel_form'] = $this->formBuilder->getForm('\Drupal\devel\Form\SwitchUserForm');
+      }
+    }
+
+    return $build;
+  }
+
+  /**
+   * Provides the list of accounts that can be used for the user switch.
+   *
+   * Inactive users are omitted from all of the following db selects. Users
+   * with 'switch users' permission and anonymous user if include_anon property
+   * is set to TRUE, are prioritized.
+   *
+   * @return \Drupal\Core\Session\AccountInterface[]
+   *   List of accounts to be used for the switch.
+   */
+  protected function getUsers() {
+    $list_size = $this->configuration['list_size'];
+    $include_anonymous = $this->configuration['include_anon'];
+
+    $list_size = $include_anonymous ? $list_size - 1 : $list_size;
+
+    // Users with 'switch users' permission are prioritized so
+    // we try to load first users with this permission.
+    $query = $this->userStorage->getQuery()
+      ->condition('uid', 0, '>')
+      ->condition('status', 0, '>')
+      ->sort('access', 'DESC')
+      ->range(0, $list_size);
+
+    $roles = user_roles(TRUE, 'switch users');
+
+    if (!empty($roles) && !isset($roles[Role::AUTHENTICATED_ID])) {
+      $query->condition('roles', array_keys($roles), 'IN');
+    }
+
+    $user_ids = $query->execute();
+
+    // If we don't have enough users with 'switch users' permission, add
+    // uids until we hit $list_size.
+    if (count($user_ids) < $list_size) {
+      $query = $this->userStorage->getQuery()
+        ->condition('uid', 0, '>')
+        ->condition('status', 0, '>')
+        ->sort('access', 'DESC')
+        ->range(0, $list_size);
+
+      // Excludes the prioritized user ids only if the previous query return
+      // some records.
+      if (!empty($user_ids)) {
+        $query->condition('uid', array_keys($user_ids), 'NOT IN');
+        $query->range(0, $list_size - count($user_ids));
+      }
+
+      $user_ids += $query->execute();
+    }
+
+    /** @var \Drupal\Core\Session\AccountInterface[] $accounts */
+    $accounts = $this->userStorage->loadMultiple($user_ids);
+
+    if ($include_anonymous) {
+      $anonymous = new AnonymousUserSession();
+      $accounts[$anonymous->id()] = $anonymous;
+    }
+
+    uasort($accounts, 'static::sortUserList');
+
+    return $accounts;
+  }
+
+  /**
+   * Builds the user listing as renderable array.
+   *
+   * @param \Drupal\core\Session\AccountInterface[] $accounts
+   *   The accounts to be rendered in the list.
+   *
+   * @return array
+   *   A renderable array.
+   */
+  protected function buildUserList(array $accounts) {
+    $links = [];
+
+    foreach ($accounts as $account) {
+      $links[$account->id()] = [
+        'title' => $account->getDisplayName(),
+        'url' => Url::fromRoute('devel.switch', ['name' => $account->getAccountName()]),
+        'query' => $this->getDestinationArray(),
+        'attributes' => [
+          'title' => $account->hasPermission('switch users') ? $this->t('This user can switch back.') : $this->t('Caution: this user will be unable to switch back.'),
+        ],
+      ];
+
+      if ($account->isAnonymous()) {
+        $links[$account->id()]['url'] = Url::fromRoute('user.logout');
+      }
+
+      if ($this->currentUser->id() === $account->id()) {
+        $links[$account->id()]['title'] = new FormattableMarkup('%user', ['%user' => $account->getDisplayName()]);
+      }
+    }
+
+    return [
+      '#theme' => 'links',
+      '#links' => $links,
+      '#attached' => ['library' => ['devel/devel']],
+    ];
+  }
+
+  /**
+   * Helper callback for uasort() to sort accounts by last access.
+   *
+   * @param \Drupal\Core\Session\AccountInterface $a
+   *   First account.
+   * @param \Drupal\Core\Session\AccountInterface $b
+   *   Second account.
+   *
+   * @return int
+   *   Result of comparing the last access times:
+   *   - -1 if $a was more recently accessed
+   *   -  0 if last access times compare equal
+   *   -  1 if $b was more recently accessed
+   */
+  public static function sortUserList(AccountInterface $a, AccountInterface $b) {
+    $a_access = (int) $a->getLastAccessedTime();
+    $b_access = (int) $b->getLastAccessedTime();
+
+    if ($a_access === $b_access) {
+      return 0;
+    }
+
+    // User never access to site.
+    if ($a_access === 0) {
+      return 1;
+    }
+
+    return ($a_access > $b_access) ? -1 : 1;
+  }
+
+}
diff --git a/web/modules/contrib/devel/src/Plugin/Derivative/DevelLocalTask.php b/web/modules/contrib/devel/src/Plugin/Derivative/DevelLocalTask.php
new file mode 100644
index 000000000..4dedb38d3
--- /dev/null
+++ b/web/modules/contrib/devel/src/Plugin/Derivative/DevelLocalTask.php
@@ -0,0 +1,106 @@
+entityTypeManager = $entity_type_manager;
+    $this->stringTranslation = $string_translation;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container, $base_plugin_id) {
+    return new static(
+      $container->get('entity_type.manager'),
+      $container->get('string_translation')
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getDerivativeDefinitions($base_plugin_definition) {
+    $this->derivatives = [];
+
+    foreach ($this->entityTypeManager->getDefinitions() as $entity_type_id => $entity_type) {
+
+      $has_edit_path = $entity_type->hasLinkTemplate('devel-load');
+      $has_canonical_path = $entity_type->hasLinkTemplate('devel-render');
+
+      if ($has_edit_path || $has_canonical_path) {
+
+        $this->derivatives["$entity_type_id.devel_tab"] = [
+          'route_name' => "entity.$entity_type_id." . ($has_edit_path ? 'devel_load' : 'devel_render'),
+          'title' => $this->t('Devel'),
+          'base_route' => "entity.$entity_type_id." . ($has_canonical_path ? "canonical" : "edit_form"),
+          'weight' => 100,
+        ];
+
+        $this->derivatives["$entity_type_id.devel_definition_tab"] = [
+          'route_name' => "entity.$entity_type_id.devel_definition",
+          'title' => $this->t('Definition'),
+          'parent_id' => "devel.entities:$entity_type_id.devel_tab",
+          'weight' => 100,
+        ];
+
+        if ($has_canonical_path) {
+          $this->derivatives["$entity_type_id.devel_render_tab"] = [
+            'route_name' => "entity.$entity_type_id.devel_render",
+            'weight' => 100,
+            'title' => $this->t('Render'),
+            'parent_id' => "devel.entities:$entity_type_id.devel_tab",
+          ];
+        }
+
+        if ($has_edit_path) {
+          $this->derivatives["$entity_type_id.devel_load_tab"] = [
+            'route_name' => "entity.$entity_type_id.devel_load",
+            'weight' => 100,
+            'title' => $this->t('Load'),
+            'parent_id' => "devel.entities:$entity_type_id.devel_tab",
+          ];
+        }
+      }
+    }
+
+    foreach ($this->derivatives as &$entry) {
+      $entry += $base_plugin_definition;
+    }
+
+    return $this->derivatives;
+  }
+
+}
diff --git a/web/modules/contrib/devel/src/Plugin/Devel/Dumper/DoctrineDebug.php b/web/modules/contrib/devel/src/Plugin/Devel/Dumper/DoctrineDebug.php
new file mode 100644
index 000000000..b827d4504
--- /dev/null
+++ b/web/modules/contrib/devel/src/Plugin/Devel/Dumper/DoctrineDebug.php
@@ -0,0 +1,71 @@
+Doctrine debugging tool.")
+ * )
+ */
+class DoctrineDebug extends DevelDumperBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function export($input, $name = NULL) {
+    $name = $name ? $name . ' => ' : '';
+    $variable = Debug::export($input, 6);
+
+    ob_start();
+    print_r($variable);
+    $dump = ob_get_contents();
+    ob_end_clean();
+
+    // Run Xss::filterAdmin on the resulting string to prevent
+    // cross-site-scripting (XSS) vulnerabilities.
+    $dump = Xss::filterAdmin($dump);
+
+    $config = \Drupal::config('devel.settings');
+    $debug_pre = $config->get('debug_pre');
+    $dump = ($debug_pre ? '
' : '') . $name . $dump . ($debug_pre ? '
' : ''); + + return $this->setSafeMarkup($dump); + } + + /** + * {@inheritdoc} + */ + public function exportAsRenderable($input, $name = NULL) { + $output['container'] = [ + '#type' => 'details', + '#title' => $name ?: $this->t('Variable'), + '#attached' => [ + 'library' => ['devel/devel'], + ], + '#attributes' => [ + 'class' => ['container-inline', 'devel-dumper', 'devel-selectable'], + ], + 'export' => [ + '#markup' => $this->export($input), + ], + ]; + + return $output; + } + + /** + * {@inheritdoc} + */ + public static function checkRequirements() { + return TRUE; + } + +} diff --git a/web/modules/contrib/devel/src/Plugin/Devel/Dumper/Kint.php b/web/modules/contrib/devel/src/Plugin/Devel/Dumper/Kint.php new file mode 100644 index 000000000..4998f9e51 --- /dev/null +++ b/web/modules/contrib/devel/src/Plugin/Devel/Dumper/Kint.php @@ -0,0 +1,100 @@ +Kint debugging tool."), + * ) + */ +class Kint extends DevelDumperBase { + + /** + * {@inheritdoc} + */ + public function __construct(array $configuration, $plugin_id, $plugin_definition) { + parent::__construct($configuration, $plugin_id, $plugin_definition); + $this->configure(); + } + + /** + * Configures kint with more sane values. + */ + protected function configure() { + // Remove resource-hungry plugins. + \Kint::$plugins = array_diff(\Kint::$plugins, [ + 'Kint\\Parser\\ClassMethodsPlugin', + 'Kint\\Parser\\ClassStaticsPlugin', + 'Kint\\Parser\\IteratorPlugin', + ]); + \Kint::$aliases = $this->getInternalFunctions(); + + RichRenderer::$folder = FALSE; + BlacklistPlugin::$shallow_blacklist[] = ContainerInterface::class; + } + + /** + * {@inheritdoc} + */ + public function export($input, $name = NULL) { + ob_start(); + if ($name == '__ARGS__') { + call_user_func_array(['Kint', 'dump'], $input); + $name = NULL; + } + elseif ($name !== NULL) { + // In order to get the correct access path information returned from Kint + // we have to give a second parameter here. This is due to a fault in + // Kint::getSingleCall which returns no info when the number of arguments + // passed to Kint::dump does not match the number in the original call + // that invoked the export (such as dsm). However, this second parameter + // is just treated as the next variable to dump, it is not used as the + // label. So we give a dummy value that we can remove below. + // @see https://gitlab.com/drupalspoons/devel/-/issues/252 + \Kint::dump($input, '---temporary-fix-see-issue-252---'); + } + else { + \Kint::dump($input); + } + $dump = ob_get_clean(); + if ($name) { + // Kint no longer treats an additional parameter as a custom title, but we + // can add the required $name as a label at the top of the output. + $dump = str_replace('
', '
' . $name . ': ', $dump); + + // Remove the output from the second dummy parameter. The pattern in [ ] + // matches the minimum to ensure we get just the string to be removed. + $pattern = '/(
[\w\d\s<>\/()]*"---temporary-fix-see-issue-252---"<\/dt><\/dl>)/'; + preg_match($pattern, $dump, $matches); + if (!preg_last_error() && isset($matches[1])) { + $dump = str_replace($matches[1], '', $dump); + } + } + + return $this->setSafeMarkup($dump); + } + + /** + * {@inheritdoc} + */ + public function getInternalFunctions() { + return array_merge(parent::getInternalFunctions(), \Kint\Kint::$aliases); + } + + /** + * {@inheritdoc} + */ + public static function checkRequirements() { + return class_exists('Kint', TRUE); + } + +} diff --git a/web/modules/contrib/devel/src/Plugin/Devel/Dumper/VarDumper.php b/web/modules/contrib/devel/src/Plugin/Devel/Dumper/VarDumper.php new file mode 100644 index 000000000..d51ae9cf3 --- /dev/null +++ b/web/modules/contrib/devel/src/Plugin/Devel/Dumper/VarDumper.php @@ -0,0 +1,46 @@ +Symfony var-dumper debugging tool."), + * ) + */ +class VarDumper extends DevelDumperBase { + + /** + * {@inheritdoc} + */ + public function export($input, $name = NULL) { + $cloner = new VarCloner(); + $dumper = 'cli' === PHP_SAPI ? new CliDumper() : new HtmlDumper(); + + $output = fopen('php://memory', 'r+b'); + $dumper->dump($cloner->cloneVar($input), $output); + $output = stream_get_contents($output, -1, 0); + + if ($name) { + $output = $name . ' => ' . $output; + } + + return $this->setSafeMarkup($output); + } + + /** + * {@inheritdoc} + */ + public static function checkRequirements() { + return class_exists('Symfony\Component\VarDumper\Cloner\VarCloner', TRUE); + } + +} diff --git a/web/modules/contrib/devel/src/Plugin/Mail/DevelMailLog.php b/web/modules/contrib/devel/src/Plugin/Mail/DevelMailLog.php new file mode 100644 index 000000000..354fcb2aa --- /dev/null +++ b/web/modules/contrib/devel/src/Plugin/Mail/DevelMailLog.php @@ -0,0 +1,203 @@ +config = $config_factory->get('devel.settings'); + $this->fileSystem = $file_system; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + return new static( + $configuration, + $plugin_id, + $plugin_definition, + $container->get('config.factory'), + $container->get('file_system') + ); + } + + /** + * {@inheritdoc} + */ + public function mail(array $message) { + $directory = $this->config->get('debug_mail_directory'); + + if (!$this->prepareDirectory($directory)) { + return FALSE; + } + + $pattern = $this->config->get('debug_mail_file_format'); + $filename = $this->replacePlaceholders($pattern, $message); + $output = $this->composeMessage($message); + + return (bool) file_put_contents($directory . '/' . $filename, $output); + } + + /** + * {@inheritdoc} + */ + public function format(array $message) { + // Join the body array into one string. + $message['body'] = implode("\n\n", $message['body']); + + // Convert any HTML to plain-text. + $message['body'] = MailFormatHelper::htmlToText($message['body']); + // Wrap the mail body for sending. + $message['body'] = MailFormatHelper::wrapMail($message['body']); + + return $message; + } + + /** + * Compose the output message. + * + * @param array $message + * A message array, as described in hook_mail_alter(). + * + * @return string + * The output message. + */ + protected function composeMessage(array $message) { + $mimeheaders = []; + $message['headers']['To'] = $message['to']; + foreach ($message['headers'] as $name => $value) { + $mimeheaders[] = $name . ': ' . Unicode::mimeHeaderEncode($value); + } + + $line_endings = Settings::get('mail_line_endings', PHP_EOL); + $output = implode($line_endings, $mimeheaders) . $line_endings; + // 'Subject:' is a mail header and should not be translated. + $output .= 'Subject: ' . $message['subject'] . $line_endings; + // Blank line to separate headers from body. + $output .= $line_endings; + $output .= preg_replace('@\r?\n@', $line_endings, $message['body']); + return $output; + } + + /** + * Replaces placeholders with sanitized values in a string. + * + * @param string $filename + * The string that contains the placeholders. The following placeholders + * are considered in the replacement: + * - %to: replaced by the email recipient value. + * - %subject: replaced by the email subject value. + * - %datetime: replaced by the current datetime in 'y-m-d_his' format. + * @param array $message + * A message array, as described in hook_mail_alter(). + * + * @return string + * The formatted string. + */ + protected function replacePlaceholders($filename, array $message) { + $tokens = [ + '%to' => $message['to'], + '%subject' => $message['subject'], + '%datetime' => date('y-m-d_his'), + ]; + $filename = str_replace(array_keys($tokens), array_values($tokens), $filename); + return preg_replace('/[^a-zA-Z0-9_\-\.@]/', '_', $filename); + } + + /** + * Checks that the directory exists and is writable. + * + * Public directories will be protected by adding an .htaccess which + * indicates that the directory is private. + * + * @param string $directory + * A string reference containing the name of a directory path or URI. + * + * @return bool + * TRUE if the directory exists (or was created), is writable and is + * protected (if it is public). FALSE otherwise. + */ + protected function prepareDirectory($directory) { + if (!$this->fileSystem->prepareDirectory($directory, FileSystemInterface::CREATE_DIRECTORY)) { + return FALSE; + } + if (0 === strpos($directory, 'public://')) { + return FileSecurity::writeHtaccess($directory); + } + + return TRUE; + } + +} diff --git a/web/modules/contrib/devel/src/Plugin/Menu/DestinationMenuLink.php b/web/modules/contrib/devel/src/Plugin/Menu/DestinationMenuLink.php new file mode 100644 index 000000000..4edf1c329 --- /dev/null +++ b/web/modules/contrib/devel/src/Plugin/Menu/DestinationMenuLink.php @@ -0,0 +1,32 @@ +')->toString(); + return $options; + } + + /** + * {@inheritdoc} + * + * @todo Make cacheable once https://www.drupal.org/node/2582797 lands. + */ + public function getCacheMaxAge() { + return 0; + } + +} diff --git a/web/modules/contrib/devel/src/Plugin/Menu/RouteDetailMenuLink.php b/web/modules/contrib/devel/src/Plugin/Menu/RouteDetailMenuLink.php new file mode 100644 index 000000000..92299180c --- /dev/null +++ b/web/modules/contrib/devel/src/Plugin/Menu/RouteDetailMenuLink.php @@ -0,0 +1,29 @@ +')->getInternalPath(); + return $options; + } + + /** + * {@inheritdoc} + */ + public function getCacheMaxAge() { + return 0; + } + +} diff --git a/web/modules/contrib/devel/src/Render/FilteredMarkup.php b/web/modules/contrib/devel/src/Render/FilteredMarkup.php new file mode 100644 index 000000000..2d144a0f5 --- /dev/null +++ b/web/modules/contrib/devel/src/Render/FilteredMarkup.php @@ -0,0 +1,23 @@ +entityTypeManager = $entity_manager; + } + + /** + * {@inheritdoc} + */ + protected function alterRoutes(RouteCollection $collection) { + foreach ($this->entityTypeManager->getDefinitions() as $entity_type_id => $entity_type) { + if ($route = $this->getEntityLoadRoute($entity_type)) { + $collection->add("entity.$entity_type_id.devel_load", $route); + } + if ($route = $this->getEntityRenderRoute($entity_type)) { + $collection->add("entity.$entity_type_id.devel_render", $route); + } + if ($route = $this->getEntityTypeDefinitionRoute($entity_type)) { + $collection->add("entity.$entity_type_id.devel_definition", $route); + } + } + } + + /** + * Gets the entity load route. + * + * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type + * The entity type. + * + * @return \Symfony\Component\Routing\Route|null + * The generated route, if available. + */ + protected function getEntityLoadRoute(EntityTypeInterface $entity_type) { + if ($devel_load = $entity_type->getLinkTemplate('devel-load')) { + $entity_type_id = $entity_type->id(); + $route = new Route($devel_load); + $route + ->addDefaults([ + '_controller' => '\Drupal\devel\Controller\EntityDebugController::entityLoad', + '_title' => 'Devel Load', + ]) + ->addRequirements([ + '_permission' => 'access devel information', + ]) + ->setOption('_admin_route', TRUE) + ->setOption('_devel_entity_type_id', $entity_type_id) + ->setOption('parameters', [ + $entity_type_id => ['type' => 'entity:' . $entity_type_id], + ]); + + return $route; + } + } + + /** + * Gets the entity render route. + * + * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type + * The entity type. + * + * @return \Symfony\Component\Routing\Route|null + * The generated route, if available. + */ + protected function getEntityRenderRoute(EntityTypeInterface $entity_type) { + if ($devel_render = $entity_type->getLinkTemplate('devel-render')) { + $entity_type_id = $entity_type->id(); + $route = new Route($devel_render); + $route + ->addDefaults([ + '_controller' => '\Drupal\devel\Controller\EntityDebugController::entityRender', + '_title' => 'Devel Render', + ]) + ->addRequirements([ + '_permission' => 'access devel information', + ]) + ->setOption('_admin_route', TRUE) + ->setOption('_devel_entity_type_id', $entity_type_id) + ->setOption('parameters', [ + $entity_type_id => ['type' => 'entity:' . $entity_type_id], + ]); + + return $route; + } + } + + /** + * Gets the entity type definition route. + * + * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type + * The entity type. + * + * @return \Symfony\Component\Routing\Route|null + * The generated route, if available. + */ + protected function getEntityTypeDefinitionRoute(EntityTypeInterface $entity_type) { + if ($devel_definition = $entity_type->getLinkTemplate('devel-definition')) { + $entity_type_id = $entity_type->id(); + $route = new Route($devel_definition); + $route + ->addDefaults([ + '_controller' => '\Drupal\devel\Controller\EntityDebugController::entityTypeDefinition', + '_title' => 'Entity type definition', + ]) + ->addRequirements([ + '_permission' => 'access devel information', + ]) + ->setOption('_admin_route', TRUE) + ->setOption('_devel_entity_type_id', $entity_type_id) + ->setOption('parameters', [ + $entity_type_id => ['type' => 'entity:' . $entity_type_id], + ]); + + return $route; + } + } + + /** + * {@inheritdoc} + */ + public static function getSubscribedEvents() { + $events = parent::getSubscribedEvents(); + $events[RoutingEvents::ALTER] = ['onAlterRoutes', 100]; + return $events; + } + +} diff --git a/web/modules/contrib/devel/src/ToolbarHandler.php b/web/modules/contrib/devel/src/ToolbarHandler.php new file mode 100644 index 000000000..cfdb955d3 --- /dev/null +++ b/web/modules/contrib/devel/src/ToolbarHandler.php @@ -0,0 +1,189 @@ +menuLinkTree = $menu_link_tree; + $this->config = $config_factory->get('devel.toolbar.settings'); + $this->account = $account; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('toolbar.menu_tree'), + $container->get('config.factory'), + $container->get('current_user') + ); + } + + /** + * {@inheritdoc} + */ + public static function trustedCallbacks() { + return ['lazyBuilder']; + } + + /** + * Hook bridge. + * + * @return array + * The devel toolbar items render array. + * + * @see hook_toolbar() + */ + public function toolbar() { + $items['devel'] = [ + '#cache' => [ + 'contexts' => ['user.permissions'], + ], + ]; + + if ($this->account->hasPermission('access devel information')) { + $items['devel'] += [ + '#type' => 'toolbar_item', + '#weight' => 999, + 'tab' => [ + '#type' => 'link', + '#title' => $this->t('Devel'), + '#url' => Url::fromRoute('devel.admin_settings'), + '#attributes' => [ + 'title' => $this->t('Development menu'), + 'class' => ['toolbar-icon', 'toolbar-icon-devel'], + ], + ], + 'tray' => [ + '#heading' => $this->t('Development menu'), + 'devel_menu' => [ + // Currently devel menu is uncacheable, so instead of poisoning the + // entire page cache we use a lazy builder. + // @see \Drupal\devel\Plugin\Menu\DestinationMenuLink + // @see \Drupal\devel\Plugin\Menu\RouteDetailMenuItem + '#lazy_builder' => [ToolbarHandler::class . ':lazyBuilder', []], + // Force the creation of the placeholder instead of rely on the + // automatical placeholdering or otherwise the page results + // uncacheable when max-age 0 is bubbled up. + '#create_placeholder' => TRUE, + ], + 'configuration' => [ + '#type' => 'link', + '#title' => $this->t('Configure'), + '#url' => Url::fromRoute('devel.toolbar.settings_form'), + '#options' => [ + 'attributes' => ['class' => ['edit-devel-toolbar']], + ], + ], + ], + '#attached' => [ + 'library' => 'devel/devel-toolbar', + ], + ]; + } + + return $items; + } + + /** + * Lazy builder callback for the devel menu toolbar. + * + * @return array + * The renderable array rapresentation of the devel menu. + */ + public function lazyBuilder() { + $parameters = new MenuTreeParameters(); + $parameters->onlyEnabledLinks()->setTopLevelOnly(); + + $tree = $this->menuLinkTree->load('devel', $parameters); + + $manipulators = [ + ['callable' => 'menu.default_tree_manipulators:checkAccess'], + ['callable' => 'menu.default_tree_manipulators:generateIndexAndSort'], + ['callable' => ToolbarHandler::class . ':processTree'], + ]; + $tree = $this->menuLinkTree->transform($tree, $manipulators); + + $build = $this->menuLinkTree->build($tree); + + CacheableMetadata::createFromRenderArray($build) + ->addCacheableDependency($this->config) + ->applyTo($build); + + return $build; + } + + /** + * Adds toolbar-specific attributes to the menu link tree. + * + * @param \Drupal\Core\Menu\MenuLinkTreeElement[] $tree + * The menu link tree to manipulate. + * + * @return \Drupal\Core\Menu\MenuLinkTreeElement[] + * The manipulated menu link tree. + */ + public function processTree(array $tree) { + $visible_items = $this->config->get('toolbar_items') ?: []; + + foreach ($tree as $element) { + $plugin_id = $element->link->getPluginId(); + if (!in_array($plugin_id, $visible_items)) { + // Add a class that allow to hide the non prioritized menu items when + // the toolbar has horizontal orientation. + $element->options['attributes']['class'][] = 'toolbar-horizontal-item-hidden'; + } + } + + return $tree; + } + +} diff --git a/web/modules/contrib/devel/src/Twig/Extension/Debug.php b/web/modules/contrib/devel/src/Twig/Extension/Debug.php new file mode 100644 index 000000000..e9f7e168e --- /dev/null +++ b/web/modules/contrib/devel/src/Twig/Extension/Debug.php @@ -0,0 +1,243 @@ +dumper = $dumper; + } + + /** + * {@inheritdoc} + */ + public function getName() { + return 'devel_debug'; + } + + /** + * {@inheritdoc} + */ + public function getFunctions() { + $options = [ + 'is_safe' => ['html'], + 'needs_environment' => TRUE, + 'needs_context' => TRUE, + 'is_variadic' => TRUE, + ]; + + return [ + new \Twig_SimpleFunction('devel_dump', [$this, 'dump'], $options), + new \Twig_SimpleFunction('kpr', [$this, 'dump'], $options), + // Preserve familiar kint() function for dumping + new \Twig_SimpleFunction('kint', [$this, 'dump'], $options), + new \Twig_SimpleFunction('devel_message', [$this, 'message'], $options), + new \Twig_SimpleFunction('dpm', [$this, 'message'], $options), + new \Twig_SimpleFunction('dsm', [$this, 'message'], $options), + new \Twig_SimpleFunction('devel_breakpoint', [$this, 'breakpoint'], [ + 'needs_environment' => TRUE, + 'needs_context' => TRUE, + 'is_variadic' => TRUE, + ]), + ]; + } + + /** + * Provides debug function to Twig templates. + * + * Handles 0, 1, or multiple arguments. + * + * @param \Twig_Environment $env + * The twig environment instance. + * @param array $context + * An array of parameters passed to the template. + * @param array $args + * An array of parameters passed the function. + * + * @return string + * String representation of the input variables. + * + * @see \Drupal\devel\DevelDumperManager::dump() + */ + public function dump(\Twig_Environment $env, array $context, array $args = []) { + if (!$env->isDebug()) { + return NULL; + } + + ob_start(); + + // No arguments passed, display full Twig context. + if (empty($args)) { + $context_variables = $this->getContextVariables($context); + $this->dumper->dump($context_variables, 'Twig context'); + } + else { + $parameters = $this->guessTwigFunctionParameters(); + + foreach ($args as $index => $variable) { + $name = !empty($parameters[$index]) ? $parameters[$index] : NULL; + $this->dumper->dump($variable, $name); + } + } + + return ob_get_clean(); + } + + /** + * Provides debug function to Twig templates. + * + * Handles 0, 1, or multiple arguments. + * + * @param \Twig_Environment $env + * The twig environment instance. + * @param array $context + * An array of parameters passed to the template. + * @param array $args + * An array of parameters passed the function. + * + * @see \Drupal\devel\DevelDumperManager::message() + */ + public function message(\Twig_Environment $env, array $context, array $args = []) { + if (!$env->isDebug()) { + return; + } + + // No arguments passed, display full Twig context. + if (empty($args)) { + $context_variables = $this->getContextVariables($context); + $this->dumper->message($context_variables, 'Twig context'); + } + else { + $parameters = $this->guessTwigFunctionParameters(); + + foreach ($args as $index => $variable) { + $name = !empty($parameters[$index]) ? $parameters[$index] : NULL; + $this->dumper->message($variable, $name); + } + } + + } + + /** + * Provides XDebug integration for Twig templates. + * + * To use this features simply put the following statement in the template + * of interest: + * + * @code + * {{ devel_breakpoint() }} + * @endcode + * + * When the template is evaluated is made a call to a dedicated method in + * devel twig debug extension in which is used xdebug_break(), that emits a + * breakpoint to the debug client (the debugger break on the specific line as + * if a normal file/line breakpoint was set on this line). + * In this way you'll be able to inspect any variables available in the + * template (environment, context, specific variables etc..) in your IDE. + * + * @param \Twig_Environment $env + * The twig environment instance. + * @param array $context + * An array of parameters passed to the template. + * @param array $args + * An array of parameters passed the function. + */ + public function breakpoint(\Twig_Environment $env, array $context, array $args = []) { + if (!$env->isDebug()) { + return; + } + + if (function_exists('xdebug_break')) { + xdebug_break(); + } + } + + /** + * Filters the Twig context variable. + * + * @param array $context + * The Twig context. + * + * @return array + * An array Twig context variables. + */ + protected function getContextVariables(array $context) { + $context_variables = []; + foreach ($context as $key => $value) { + if (!$value instanceof \Twig_Template) { + $context_variables[$key] = $value; + } + } + return $context_variables; + } + + /** + * Gets the twig function parameters for the current invocation. + * + * @return array + * The detected twig function parameters. + */ + protected function guessTwigFunctionParameters() { + $callee = NULL; + $template = NULL; + + $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS | DEBUG_BACKTRACE_PROVIDE_OBJECT); + + foreach ($backtrace as $index => $trace) { + if (isset($trace['object']) && $trace['object'] instanceof \Twig_Template && 'Twig_Template' !== get_class($trace['object'])) { + $template = $trace['object']; + $callee = $backtrace[$index - 1]; + break; + } + } + + $parameters = []; + + /** @var \Twig_Template $template */ + if (NULL !== $template && NULL !== $callee) { + $line_number = $callee['line']; + $debug_infos = $template->getDebugInfo(); + + if (isset($debug_infos[$line_number])) { + $source_line = $debug_infos[$line_number]; + $source_file_name = $template->getTemplateName(); + + if (is_readable($source_file_name)) { + $source = file($source_file_name, FILE_IGNORE_NEW_LINES); + $line = $source[$source_line - 1]; + + preg_match('/\((.+)\)/', $line, $matches); + if (isset($matches[1])) { + $parameters = array_map('trim', explode(',', $matches[1])); + } + } + } + } + + return $parameters; + } + +} diff --git a/web/modules/contrib/devel/tests/modules/devel_dumper_test/css/devel_dumper_test.css b/web/modules/contrib/devel/tests/modules/devel_dumper_test/css/devel_dumper_test.css new file mode 100644 index 000000000..62a91dbc6 --- /dev/null +++ b/web/modules/contrib/devel/tests/modules/devel_dumper_test/css/devel_dumper_test.css @@ -0,0 +1,3 @@ +.some-rule { + display: block; +} diff --git a/web/modules/contrib/devel/tests/modules/devel_dumper_test/devel_dumper_test.info.yml b/web/modules/contrib/devel/tests/modules/devel_dumper_test/devel_dumper_test.info.yml new file mode 100644 index 000000000..da40934e2 --- /dev/null +++ b/web/modules/contrib/devel/tests/modules/devel_dumper_test/devel_dumper_test.info.yml @@ -0,0 +1,10 @@ +name: 'Devel dumper test module' +type: module +description: 'Test pluggable dumpers.' +package: Testing +core_version_requirement: ^8.8 || ^9 + +# Information added by Drupal.org packaging script on 2020-12-31 +version: '4.1.1' +project: 'devel' +datestamp: 1609419530 diff --git a/web/modules/contrib/devel/tests/modules/devel_dumper_test/devel_dumper_test.libraries.yml b/web/modules/contrib/devel/tests/modules/devel_dumper_test/devel_dumper_test.libraries.yml new file mode 100644 index 000000000..8e8c2ad88 --- /dev/null +++ b/web/modules/contrib/devel/tests/modules/devel_dumper_test/devel_dumper_test.libraries.yml @@ -0,0 +1,7 @@ +devel_dumper_test: + version: 0 + css: + theme: + css/devel_dumper_test.css: {} + js: + js/devel_dumper_test.js: {} diff --git a/web/modules/contrib/devel/tests/modules/devel_dumper_test/devel_dumper_test.routing.yml b/web/modules/contrib/devel/tests/modules/devel_dumper_test/devel_dumper_test.routing.yml new file mode 100644 index 000000000..589193b87 --- /dev/null +++ b/web/modules/contrib/devel/tests/modules/devel_dumper_test/devel_dumper_test.routing.yml @@ -0,0 +1,40 @@ +devel_dumper_test.dump: + path: '/devel_dumper_test/dump' + defaults: + _controller: '\Drupal\devel_dumper_test\Controller\DumperTestController::dump' + _title: 'Devel Dumper Test' + requirements: + _permission: 'access devel information' + +devel_dumper_test.message: + path: '/devel_dumper_test/message' + defaults: + _controller: '\Drupal\devel_dumper_test\Controller\DumperTestController::message' + _title: 'Devel Dumper Test' + requirements: + _permission: 'access devel information' + +devel_dumper_test.export: + path: '/devel_dumper_test/export' + defaults: + _controller: '\Drupal\devel_dumper_test\Controller\DumperTestController::export' + _title: 'Devel Dumper Test' + requirements: + _permission: 'access devel information' + +devel_dumper_test.export_renderable: + path: '/devel_dumper_test/export_renderable' + defaults: + _controller: '\Drupal\devel_dumper_test\Controller\DumperTestController::exportRenderable' + _title: 'Devel Dumper Test' + requirements: + _permission: 'access devel information' + +devel_dumper_test.debug: + path: '/devel_dumper_test/debug' + defaults: + _controller: '\Drupal\devel_dumper_test\Controller\DumperTestController::debug' + _title: 'Devel Dumper Test' + requirements: + # Specifically give access to all users for testing in testDumpersOutput(). + _access: 'TRUE' diff --git a/web/modules/contrib/devel/tests/modules/devel_dumper_test/js/devel_dumper_test.js b/web/modules/contrib/devel/tests/modules/devel_dumper_test/js/devel_dumper_test.js new file mode 100644 index 000000000..e69de29bb diff --git a/web/modules/contrib/devel/tests/modules/devel_dumper_test/src/Controller/DumperTestController.php b/web/modules/contrib/devel/tests/modules/devel_dumper_test/src/Controller/DumperTestController.php new file mode 100644 index 000000000..71e4c4247 --- /dev/null +++ b/web/modules/contrib/devel/tests/modules/devel_dumper_test/src/Controller/DumperTestController.php @@ -0,0 +1,106 @@ +dumper = $devel_dumper_manager; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('devel.dumper') + ); + } + + /** + * Returns the dump output to test. + * + * @return array + * The render array output. + */ + public function dump() { + $this->dumper->dump('Test output'); + + return [ + '#markup' => 'test', + ]; + } + + /** + * Returns the message output to test. + * + * @return array + * The render array output. + */ + public function message() { + $this->dumper->message('Test output'); + + return [ + '#markup' => 'test', + ]; + } + + /** + * Returns the debug output to test. + * + * @return array + * The render array output. + */ + public function debug() { + $this->dumper->debug('Test output'); + + return [ + '#markup' => 'test', + ]; + } + + /** + * Returns the export output to test. + * + * @return array + * The render array output. + */ + public function export() { + return [ + '#markup' => $this->dumper->export('Test output'), + ]; + } + + /** + * Returns the renderable export output to test. + * + * @return array + * The render array output. + */ + public function exportRenderable() { + return $this->dumper->exportAsRenderable('Test output'); + } + +} diff --git a/web/modules/contrib/devel/tests/modules/devel_dumper_test/src/Plugin/Devel/Dumper/AvailableTestDumper.php b/web/modules/contrib/devel/tests/modules/devel_dumper_test/src/Plugin/Devel/Dumper/AvailableTestDumper.php new file mode 100644 index 000000000..526c8d40e --- /dev/null +++ b/web/modules/contrib/devel/tests/modules/devel_dumper_test/src/Plugin/Devel/Dumper/AvailableTestDumper.php @@ -0,0 +1,61 @@ +' . 'AvailableTestDumper::dump() ' . $input . '
'; + echo $input; + } + + /** + * {@inheritdoc} + */ + public function export($input, $name = NULL) { + // Add a predetermined string to $input to check if this dumper has been + // selected successfully. + $input = '
' . 'AvailableTestDumper::export() ' . $input . '
'; + return $this->setSafeMarkup($input); + } + + /** + * {@inheritdoc} + */ + public function exportAsRenderable($input, $name = NULL) { + // Add a predetermined string to $input to check if this dumper has been + // selected successfully. + $input = '
' . 'AvailableTestDumper::exportAsRenderable() ' . $input . '
'; + + return [ + '#attached' => [ + 'library' => ['devel_dumper_test/devel_dumper_test'], + ], + '#markup' => $this->setSafeMarkup($input), + ]; + } + + /** + * {@inheritdoc} + */ + public static function checkRequirements() { + return TRUE; + } + +} diff --git a/web/modules/contrib/devel/tests/modules/devel_dumper_test/src/Plugin/Devel/Dumper/NotAvailableTestDumper.php b/web/modules/contrib/devel/tests/modules/devel_dumper_test/src/Plugin/Devel/Dumper/NotAvailableTestDumper.php new file mode 100644 index 000000000..2b0d886ee --- /dev/null +++ b/web/modules/contrib/devel/tests/modules/devel_dumper_test/src/Plugin/Devel/Dumper/NotAvailableTestDumper.php @@ -0,0 +1,41 @@ +' . $input . ''; + echo $input; + } + + /** + * {@inheritdoc} + */ + public function export($input, $name = NULL) { + $input = '
' . $input . '
'; + return $this->setSafeMarkup($input); + } + + /** + * {@inheritdoc} + */ + public static function checkRequirements() { + return FALSE; + } + +} diff --git a/web/modules/contrib/devel/tests/modules/devel_entity_test/devel_entity_test.info.yml b/web/modules/contrib/devel/tests/modules/devel_entity_test/devel_entity_test.info.yml new file mode 100644 index 000000000..157eca0bc --- /dev/null +++ b/web/modules/contrib/devel/tests/modules/devel_entity_test/devel_entity_test.info.yml @@ -0,0 +1,14 @@ +name: 'Devel entity test module' +type: module +description: 'Provides entity types for Devel tests.' +package: Testing +core_version_requirement: ^8.8 || ^9 +dependencies: + - drupal:field + - drupal:text + - drupal:entity_test + +# Information added by Drupal.org packaging script on 2020-12-31 +version: '4.1.1' +project: 'devel' +datestamp: 1609419530 diff --git a/web/modules/contrib/devel/tests/modules/devel_entity_test/devel_entity_test.links.task.yml b/web/modules/contrib/devel/tests/modules/devel_entity_test/devel_entity_test.links.task.yml new file mode 100644 index 000000000..052baa9c8 --- /dev/null +++ b/web/modules/contrib/devel/tests/modules/devel_entity_test/devel_entity_test.links.task.yml @@ -0,0 +1,2 @@ +devel_entity_test.local_tasks: + deriver: 'Drupal\devel_entity_test\Plugin\Derivative\DevelEntityTestLocalTasks' diff --git a/web/modules/contrib/devel/tests/modules/devel_entity_test/devel_entity_test.module b/web/modules/contrib/devel/tests/modules/devel_entity_test/devel_entity_test.module new file mode 100644 index 000000000..dd88c4929 --- /dev/null +++ b/web/modules/contrib/devel/tests/modules/devel_entity_test/devel_entity_test.module @@ -0,0 +1,29 @@ +getDefinitions(); + foreach ($entity_info as $entity_type => $info) { + if ($entity_info[$entity_type]->getProvider() == 'devel_entity_test_canonical' && !isset($view_modes[$entity_type])) { + $view_modes[$entity_type] = [ + 'full' => [ + 'label' => t('Full object'), + 'status' => TRUE, + 'cache' => TRUE, + ], + 'teaser' => [ + 'label' => t('Teaser'), + 'status' => TRUE, + 'cache' => TRUE, + ], + ]; + } + } +} diff --git a/web/modules/contrib/devel/tests/modules/devel_entity_test/src/Entity/DevelEntityTestCanonical.php b/web/modules/contrib/devel/tests/modules/devel_entity_test/src/Entity/DevelEntityTestCanonical.php new file mode 100644 index 000000000..e7ed04f3f --- /dev/null +++ b/web/modules/contrib/devel/tests/modules/devel_entity_test/src/Entity/DevelEntityTestCanonical.php @@ -0,0 +1,44 @@ +derivatives = []; + + $this->derivatives['devel_entity_test_canonical.canonical'] = []; + $this->derivatives['devel_entity_test_canonical.canonical']['base_route'] = "entity.devel_entity_test_canonical.canonical"; + $this->derivatives['devel_entity_test_canonical.canonical']['route_name'] = "entity.devel_entity_test_canonical.canonical"; + $this->derivatives['devel_entity_test_canonical.canonical']['title'] = 'View'; + + $this->derivatives['devel_entity_test_edit.edit'] = []; + $this->derivatives['devel_entity_test_edit.edit']['base_route'] = "entity.devel_entity_test_edit.edit_form"; + $this->derivatives['devel_entity_test_edit.edit']['route_name'] = "entity.devel_entity_test_edit.edit_form"; + $this->derivatives['devel_entity_test_edit.edit']['title'] = 'Edit'; + + return parent::getDerivativeDefinitions($base_plugin_definition); + } + +} diff --git a/web/modules/contrib/devel/tests/modules/devel_test/devel_test.info.yml b/web/modules/contrib/devel/tests/modules/devel_test/devel_test.info.yml new file mode 100644 index 000000000..c1d52022a --- /dev/null +++ b/web/modules/contrib/devel/tests/modules/devel_test/devel_test.info.yml @@ -0,0 +1,10 @@ +name: 'Devel test module' +type: module +description: 'Support module for Devel testing.' +package: Testing +core_version_requirement: ^8.8 || ^9 + +# Information added by Drupal.org packaging script on 2020-12-31 +version: '4.1.1' +project: 'devel' +datestamp: 1609419530 diff --git a/web/modules/contrib/devel/tests/modules/devel_test/devel_test.module b/web/modules/contrib/devel/tests/modules/devel_test/devel_test.module new file mode 100644 index 000000000..e94fb715b --- /dev/null +++ b/web/modules/contrib/devel/tests/modules/devel_test/devel_test.module @@ -0,0 +1,20 @@ + $this->t('Simple page'), + ]; + } + +} diff --git a/web/modules/contrib/devel/tests/modules/devel_test/src/Routing/TestRouteSubscriber.php b/web/modules/contrib/devel/tests/modules/devel_test/src/Routing/TestRouteSubscriber.php new file mode 100644 index 000000000..636a38fb7 --- /dev/null +++ b/web/modules/contrib/devel/tests/modules/devel_test/src/Routing/TestRouteSubscriber.php @@ -0,0 +1,38 @@ +state = $state; + } + + /** + * {@inheritdoc} + */ + protected function alterRoutes(RouteCollection $collection) { + $this->state->set('devel_test_route_rebuild', 'Router rebuild fired'); + } + +} diff --git a/web/modules/contrib/devel/tests/src/Functional/DevelBrowserTestBase.php b/web/modules/contrib/devel/tests/src/Functional/DevelBrowserTestBase.php new file mode 100644 index 000000000..38571f85e --- /dev/null +++ b/web/modules/contrib/devel/tests/src/Functional/DevelBrowserTestBase.php @@ -0,0 +1,55 @@ +adminUser = $this->drupalCreateUser([ + 'access devel information', + 'administer site configuration', + ]); + + $this->develUser = $this->drupalCreateUser([ + 'access devel information', + ]); + } + +} diff --git a/web/modules/contrib/devel/tests/src/Functional/DevelCommandsTest.php b/web/modules/contrib/devel/tests/src/Functional/DevelCommandsTest.php new file mode 100644 index 000000000..0361dd742 --- /dev/null +++ b/web/modules/contrib/devel/tests/src/Functional/DevelCommandsTest.php @@ -0,0 +1,44 @@ +drush('devel:token', [], ['format' => 'json']); + $output = $this->getOutputFromJSON(); + $tokens = array_column($output, 'token'); + $this->assertContains('account-name', $tokens); + + $this->drush('devel:services', [], ['format' => 'json']); + $output = $this->getOutputFromJSON(); + $this->assertContains('current_user', $output); + } + +} diff --git a/web/modules/contrib/devel/tests/src/Functional/DevelContainerInfoTest.php b/web/modules/contrib/devel/tests/src/Functional/DevelContainerInfoTest.php new file mode 100644 index 000000000..938d71bb8 --- /dev/null +++ b/web/modules/contrib/devel/tests/src/Functional/DevelContainerInfoTest.php @@ -0,0 +1,245 @@ +drupalPlaceBlock('local_tasks_block'); + $this->drupalPlaceBlock('page_title_block'); + $this->drupalLogin($this->develUser); + } + + /** + * Tests container info menu link. + */ + public function testContainerInfoMenuLink() { + $this->drupalPlaceBlock('system_menu_block:devel'); + // Ensures that the events info link is present on the devel menu and that + // it points to the correct page. + $this->drupalGet(''); + $this->clickLink('Container Info'); + $this->assertSession()->statusCodeEquals(200); + $this->assertSession()->addressEquals('/devel/container/service'); + $this->assertSession()->pageTextContains('Container services'); + } + + /** + * Tests service list page. + */ + public function testServiceList() { + $this->drupalGet('/devel/container/service'); + $this->assertSession()->statusCodeEquals(200); + $this->assertSession()->pageTextContains('Container services'); + $this->assertContainerInfoLocalTasks(); + + $page = $this->getSession()->getPage(); + + // Ensures that the services table is found. + $table = $page->find('css', 'table.devel-service-list'); + $this->assertNotNull($table); + + // Ensures that the expected table headers are found. + /* @var $headers \Behat\Mink\Element\NodeElement[] */ + $headers = $table->findAll('css', 'thead th'); + $this->assertEquals(4, count($headers)); + + $expected_headers = ['ID', 'Class', 'Alias', 'Operations']; + $actual_headers = array_map(function ($element) { + return $element->getText(); + }, $headers); + $this->assertSame($expected_headers, $actual_headers); + + // Ensures that all the serivices are listed in the table. + $cached_definition = \Drupal::service('kernel')->getCachedContainerDefinition(); + $this->assertNotNull($cached_definition); + $rows = $table->findAll('css', 'tbody tr'); + $this->assertEquals(count($cached_definition['services']), count($rows)); + + // Tests the presence of some (arbitrarily chosen) services in the table. + $expected_services = [ + 'config.factory' => [ + 'class' => 'Drupal\Core\Config\ConfigFactory', + 'alias' => '', + ], + 'devel.route_subscriber' => [ + 'class' => 'Drupal\devel\Routing\RouteSubscriber', + 'alias' => '', + ], + 'plugin.manager.element_info' => [ + 'class' => 'Drupal\Core\Render\ElementInfoManager', + 'alias' => 'element_info', + ], + ]; + + foreach ($expected_services as $service_id => $expected) { + $row = $table->find('css', sprintf('tbody tr:contains("%s")', $service_id)); + $this->assertNotNull($row); + + /* @var $cells \Behat\Mink\Element\NodeElement[] */ + $cells = $row->findAll('css', 'td'); + $this->assertEquals(4, count($cells)); + + $cell_service_id = $cells[0]; + $this->assertEquals($service_id, $cell_service_id->getText()); + $this->assertTrue($cell_service_id->hasClass('table-filter-text-source')); + + $cell_class = $cells[1]; + $this->assertEquals($expected['class'], $cell_class->getText()); + $this->assertTrue($cell_class->hasClass('table-filter-text-source')); + + $cell_alias = $cells[2]; + $this->assertEquals($expected['alias'], $cell_alias->getText()); + $this->assertTrue($cell_class->hasClass('table-filter-text-source')); + + $cell_operations = $cells[3]; + $actual_href = $cell_operations->findLink('Devel')->getAttribute('href'); + $expected_href = Url::fromRoute('devel.container_info.service.detail', ['service_id' => $service_id])->toString(); + $this->assertEquals($expected_href, $actual_href); + } + + // Ensures that the page is accessible ony to users with the adequate + // permissions. + $this->drupalLogout(); + $this->drupalGet('devel/container/service'); + $this->assertSession()->statusCodeEquals(403); + } + + /** + * Tests service detail page. + */ + public function testServiceDetail() { + $service_id = 'devel.dumper'; + + // Ensures that the page works as expected. + $this->drupalGet("/devel/container/service/$service_id"); + $this->assertSession()->statusCodeEquals(200); + $this->assertSession()->pageTextContains("Service $service_id detail"); + + // Ensures that the page returns a 404 error if the requested service is + // not defined. + $this->drupalGet('/devel/container/service/not.exists'); + $this->assertSession()->statusCodeEquals(404); + + // Ensures that the page is accessible ony to users with the adequate + // permissions. + $this->drupalLogout(); + $this->drupalGet("devel/container/service/$service_id"); + $this->assertSession()->statusCodeEquals(403); + } + + /** + * Tests parameter list page. + */ + public function testParameterList() { + // Ensures that the page works as expected. + $this->drupalGet('/devel/container/parameter'); + $this->assertSession()->statusCodeEquals(200); + $this->assertSession()->pageTextContains('Container parameters'); + $this->assertContainerInfoLocalTasks(); + + $page = $this->getSession()->getPage(); + + // Ensures that the parameters table is found. + $table = $page->find('css', 'table.devel-parameter-list'); + $this->assertNotNull($table); + + // Ensures that the expected table headers are found. + /* @var $headers \Behat\Mink\Element\NodeElement[] */ + $headers = $table->findAll('css', 'thead th'); + $this->assertEquals(2, count($headers)); + + $expected_headers = ['Name', 'Operations']; + $actual_headers = array_map(function ($element) { + return $element->getText(); + }, $headers); + $this->assertSame($expected_headers, $actual_headers); + + // Ensures that all the parameters are listed in the table. + $cached_definition = \Drupal::service('kernel')->getCachedContainerDefinition(); + $this->assertNotNull($cached_definition); + $rows = $table->findAll('css', 'tbody tr'); + $this->assertEquals(count($cached_definition['parameters']), count($rows)); + + // Tests the presence of some parameters in the table. + $expected_parameters = [ + 'container.modules', + 'cache_bins', + 'factory.keyvalue', + 'twig.config', + ]; + + foreach ($expected_parameters as $parameter_name) { + $row = $table->find('css', sprintf('tbody tr:contains("%s")', $parameter_name)); + $this->assertNotNull($row); + + /* @var $cells \Behat\Mink\Element\NodeElement[] */ + $cells = $row->findAll('css', 'td'); + $this->assertEquals(2, count($cells)); + + $cell_parameter_name = $cells[0]; + $this->assertEquals($parameter_name, $cell_parameter_name->getText()); + $this->assertTrue($cell_parameter_name->hasClass('table-filter-text-source')); + + $cell_operations = $cells[1]; + $actual_href = $cell_operations->findLink('Devel')->getAttribute('href'); + $expected_href = Url::fromRoute('devel.container_info.parameter.detail', ['parameter_name' => $parameter_name])->toString(); + $this->assertEquals($expected_href, $actual_href); + } + + // Ensures that the page is accessible ony to users with the adequate + // permissions. + $this->drupalLogout(); + $this->drupalGet('devel/container/service'); + $this->assertSession()->statusCodeEquals(403); + } + + /** + * Tests parameter detail page. + */ + public function testParameterDetail() { + $parameter_name = 'cache_bins'; + + // Ensures that the page works as expected. + $this->drupalGet("/devel/container/parameter/$parameter_name"); + $this->assertSession()->statusCodeEquals(200); + $this->assertSession()->pageTextContains("Parameter $parameter_name value"); + + // Ensures that the page returns a 404 error if the requested parameter is + // not defined. + $this->drupalGet('/devel/container/parameter/not_exists'); + $this->assertSession()->statusCodeEquals(404); + + // Ensures that the page is accessible ony to users with the adequate + // permissions. + $this->drupalLogout(); + $this->drupalGet("devel/container/service/$parameter_name"); + $this->assertSession()->statusCodeEquals(403); + } + + /** + * Asserts that container info local tasks are present. + */ + protected function assertContainerInfoLocalTasks() { + $expected_local_tasks = [ + ['devel.container_info.service', []], + ['devel.container_info.parameter', []], + ]; + + $this->assertLocalTasks($expected_local_tasks); + } + +} diff --git a/web/modules/contrib/devel/tests/src/Functional/DevelControllerTest.php b/web/modules/contrib/devel/tests/src/Functional/DevelControllerTest.php new file mode 100644 index 000000000..389e4a8a4 --- /dev/null +++ b/web/modules/contrib/devel/tests/src/Functional/DevelControllerTest.php @@ -0,0 +1,145 @@ +container->get('entity_type.manager'); + + // Create a test entity. + $random_label = $this->randomMachineName(); + $data = ['type' => 'entity_test', 'name' => $random_label]; + $this->entity = $entity_type_manager->getStorage('entity_test')->create($data); + $this->entity->save(); + + // Create a test entity with only canonical route. + $random_label = $this->randomMachineName(); + $data = ['type' => 'devel_entity_test_canonical', 'name' => $random_label]; + $this->entity_canonical = $entity_type_manager->getStorage('devel_entity_test_canonical')->create($data); + $this->entity_canonical->save(); + + // Create a test entity with only edit route. + $random_label = $this->randomMachineName(); + $data = ['type' => 'devel_entity_test_edit', 'name' => $random_label]; + $this->entity_edit = $entity_type_manager->getStorage('devel_entity_test_edit')->create($data); + $this->entity_edit->save(); + + // Create a test entity with no routes. + $random_label = $this->randomMachineName(); + $data = ['type' => 'devel_entity_test_no_links', 'name' => $random_label]; + $this->entity_no_links = $entity_type_manager->getStorage('devel_entity_test_no_links')->create($data); + $this->entity_no_links->save(); + + $this->drupalPlaceBlock('local_tasks_block'); + + $web_user = $this->drupalCreateUser([ + 'view test entity', + 'administer entity_test content', + 'access devel information', + ]); + $this->drupalLogin($web_user); + } + + /** + * Tests route generation. + */ + public function testRouteGeneration() { + // Test Devel load and render routes for entities with both route + // definitions. + $this->drupalGet('entity_test/' . $this->entity->id()); + $this->assertSession()->statusCodeEquals(200); + $this->assertSession()->LinkExists('View'); + $this->assertSession()->LinkExists('Edit'); + $this->assertSession()->LinkExists('Devel'); + $this->drupalGet('devel/entity_test/' . $this->entity->id()); + $this->assertSession()->statusCodeEquals(200); + $this->assertSession()->LinkExists('Definition'); + $this->assertSession()->LinkExists('Render'); + $this->assertSession()->LinkExists('Load'); + $this->assertSession()->linkByHrefExists('devel/entity_test/' . $this->entity->id() . '/render'); + $this->drupalGet('devel/entity_test/' . $this->entity->id() . '/render'); + $this->assertSession()->statusCodeEquals(200); + $this->assertSession()->linkByHrefExists('devel/entity_test/' . $this->entity->id() . '/definition'); + $this->drupalGet('devel/entity_test/' . $this->entity->id() . '/definition'); + $this->assertSession()->statusCodeEquals(200); + + // Test Devel load and render routes for entities with only canonical route + // definitions. + $this->drupalGet('devel_entity_test_canonical/' . $this->entity_canonical->id()); + $this->assertSession()->statusCodeEquals(200); + $this->assertSession()->LinkExists('View'); + $this->assertSession()->LinkNotExists('Edit'); + $this->assertSession()->LinkExists('Devel'); + // Use xpath with equality check on @data-drupal-link-system-path because + // assertNoLinkByHref matches on partial values and finds the other link. + $this->assertSession()->elementNotExists('xpath', + '//a[@data-drupal-link-system-path = "devel/devel_entity_test_canonical/' . $this->entity_canonical->id() . '"]'); + $this->assertSession()->elementExists('xpath', + '//a[@data-drupal-link-system-path = "devel/devel_entity_test_canonical/' . $this->entity_canonical->id() . '/render"]'); + $this->drupalGet('devel/devel_entity_test_canonical/' . $this->entity_canonical->id()); + $this->assertSession()->statusCodeEquals(404); + $this->drupalGet('devel/devel_entity_test_canonical/' . $this->entity_canonical->id() . '/render'); + $this->assertSession()->statusCodeEquals(200); + $this->assertSession()->LinkExists('Definition'); + $this->assertSession()->LinkExists('Render'); + $this->assertSession()->LinkNotExists('Load'); + $this->assertSession()->linkByHrefExists('devel/devel_entity_test_canonical/' . $this->entity_canonical->id() . '/definition'); + $this->drupalGet('devel/devel_entity_test_canonical/' . $this->entity_canonical->id() . '/definition'); + $this->assertSession()->statusCodeEquals(200); + + // Test Devel load and render routes for entities with only edit route + // definitions. + $this->drupalGet('devel_entity_test_edit/manage/' . $this->entity_edit->id()); + $this->assertSession()->statusCodeEquals(200); + $this->assertSession()->LinkNotExists('View'); + $this->assertSession()->LinkExists('Edit'); + $this->assertSession()->LinkExists('Devel'); + $this->assertSession()->linkByHrefExists('devel/devel_entity_test_edit/' . $this->entity_edit->id()); + $this->drupalGet('devel/devel_entity_test_edit/' . $this->entity_edit->id()); + $this->assertSession()->statusCodeEquals(200); + $this->assertSession()->LinkExists('Definition'); + $this->assertSession()->LinkNotExists('Render'); + $this->assertSession()->LinkExists('Load'); + $this->assertSession()->linkByHrefExists('devel/devel_entity_test_edit/' . $this->entity_edit->id() . '/definition'); + $this->assertSession()->linkByHrefNotExists('devel/devel_entity_test_edit/' . $this->entity_edit->id() . '/render'); + $this->drupalGet('devel/devel_entity_test_edit/' . $this->entity_edit->id() . '/definition'); + $this->assertSession()->statusCodeEquals(200); + $this->drupalGet('devel/devel_entity_test_edit/' . $this->entity_edit->id() . '/render'); + $this->assertSession()->statusCodeEquals(404); + + // Test Devel load and render routes for entities with no route + // definitions. + $this->drupalGet('devel_entity_test_no_links/' . $this->entity_edit->id()); + $this->assertSession()->statusCodeEquals(404); + $this->drupalGet('devel/devel_entity_test_no_links/' . $this->entity_no_links->id()); + $this->assertSession()->statusCodeEquals(404); + $this->drupalGet('devel/devel_entity_test_no_links/' . $this->entity_no_links->id() . '/render'); + $this->assertSession()->statusCodeEquals(404); + $this->drupalGet('devel/devel_entity_test_no_links/' . $this->entity_no_links->id() . '/definition'); + $this->assertSession()->statusCodeEquals(404); + } + +} diff --git a/web/modules/contrib/devel/tests/src/Functional/DevelDumperTest.php b/web/modules/contrib/devel/tests/src/Functional/DevelDumperTest.php new file mode 100644 index 000000000..812919b18 --- /dev/null +++ b/web/modules/contrib/devel/tests/src/Functional/DevelDumperTest.php @@ -0,0 +1,145 @@ +drupalLogin($this->adminUser); + } + + /** + * Test dumpers configuration page. + */ + public function testDumpersConfiguration() { + $this->drupalGet('admin/config/development/devel'); + + // Ensures that the dumper input is present on the config page. + $this->assertSession()->fieldExists('dumper'); + + // Ensures that the 'default' dumper is enabled by default. + $this->assertSession()->checkboxChecked('edit-dumper-default'); + + // Ensures that all dumpers declared by devel are present on the config page + // and that only the available dumpers are selectable. + $dumpers = [ + 'default', + 'var_dumper', + ]; + $available_dumpers = ['default', 'var_dumper']; + + foreach ($dumpers as $dumper) { + $this->assertFieldsByValue($this->xpath('//input[@type="radio" and @name="dumper"]'), $dumper); + if (in_array($dumper, $available_dumpers)) { + $this->assertFieldsByValue($this->xpath('//input[@name="dumper" and not(@disabled="disabled")]'), $dumper); + } + else { + $this->assertFieldsByValue($this->xpath('//input[@name="dumper" and @disabled="disabled"]'), $dumper); + } + } + + // Ensures that dumper plugins declared by other modules are present on the + // config page and that only the available dumpers are selectable. + $this->assertFieldsByValue($this->xpath('//input[@name="dumper"]'), 'available_test_dumper'); + $this->assertSession()->pageTextContains('Available test dumper.'); + $this->assertSession()->pageTextContains('Drupal dumper for testing purposes (available).'); + $this->assertFieldsByValue($this->xpath('//input[@name="dumper" and not(@disabled="disabled")]'), 'available_test_dumper', 'Available dumper input not is disabled.'); + + $this->assertFieldsByValue($this->xpath('//input[@name="dumper"]'), 'not_available_test_dumper'); + $this->assertSession()->pageTextContains('Not available test dumper.'); + $this->assertSession()->pageTextContains('Drupal dumper for testing purposes (not available).Not available. You may need to install external dependencies for use this plugin.'); + $this->assertFieldsByValue($this->xpath('//input[@name="dumper" and @disabled="disabled"]'), 'not_available_test_dumper', 'Non available dumper input is disabled.'); + + // Ensures that saving of the dumpers configuration works as expected. + $edit = [ + 'dumper' => 'var_dumper', + ]; + $this->drupalPostForm('admin/config/development/devel', $edit, 'Save configuration'); + $this->assertSession()->pageTextContains('The configuration options have been saved.'); + $this->assertSession()->checkboxChecked('Symfony var-dumper'); + + $config = \Drupal::config('devel.settings')->get('devel_dumper'); + $this->assertEquals('var_dumper', $config, 'The configuration options have been properly saved'); + } + + /** + * Test variable is dumped in page. + */ + public function testDumpersOutput() { + $edit = [ + 'dumper' => 'available_test_dumper', + ]; + $this->drupalPostForm('admin/config/development/devel', $edit, 'Save configuration'); + $this->assertSession()->pageTextContains('The configuration options have been saved.'); + + $this->drupalGet('devel_dumper_test/dump'); + $elements = $this->xpath('//body/pre[contains(text(), :message)]', [':message' => 'AvailableTestDumper::dump() Test output']); + $this->assertNotEmpty($elements, 'Dumped message is present.'); + + $this->drupalGet('devel_dumper_test/message'); + $elements = $this->xpath('//div[@aria-label="Status message"]/pre[contains(text(), :message)]', [':message' => 'AvailableTestDumper::export() Test output']); + $this->assertNotEmpty($elements, 'Dumped message is present.'); + + $this->drupalGet('devel_dumper_test/export'); + $elements = $this->xpath('//div[@class="layout-content"]//pre[contains(text(), :message)]', [':message' => 'AvailableTestDumper::export() Test output']); + $this->assertNotEmpty($elements, 'Dumped message is present.'); + + $this->drupalGet('devel_dumper_test/export_renderable'); + $elements = $this->xpath('//div[@class="layout-content"]//pre[contains(text(), :message)]', [':message' => 'AvailableTestDumper::exportAsRenderable() Test output']); + $this->assertNotEmpty($elements, 'Dumped message is present.'); + // Ensures that plugins can add libraries to the page when the + // ::exportAsRenderable() method is used. + $this->assertSession()->responseContains('devel_dumper_test/css/devel_dumper_test.css'); + $this->assertSession()->responseContains('devel_dumper_test/js/devel_dumper_test.js'); + + // @todo Cater for deprecated code where the replacement has not been + // backported. Remove this when support for core 8.7 is no longer required. + // @see https://www.drupal.org/project/devel/issues/3118851 + if (version_compare(\Drupal::VERSION, 8.8, '>=')) { + // For 8.8+. + $debug_filename = \Drupal::service('file_system')->getTempDirectory() . '/' . 'drupal_debug.txt'; + } + else { + // Up to 8.7. + $debug_filename = file_directory_temp() . '/drupal_debug.txt'; + } + + $this->drupalGet('devel_dumper_test/debug'); + $file_content = file_get_contents($debug_filename); + $expected = <<AvailableTestDumper::export() Test output + +EOF; + $this->assertEquals($file_content, $expected, 'Dumped message is present.'); + + // Ensures that the DevelDumperManager::debug() is not access checked and + // that the dump is written in the debug file even if the user has not the + // 'access devel information' permission. + file_put_contents($debug_filename, ''); + $this->drupalLogout(); + $this->drupalGet('devel_dumper_test/debug'); + $file_content = file_get_contents($debug_filename); + $expected = <<AvailableTestDumper::export() Test output + +EOF; + $this->assertEquals($file_content, $expected, 'Dumped message is present.'); + } + +} diff --git a/web/modules/contrib/devel/tests/src/Functional/DevelElementInfoTest.php b/web/modules/contrib/devel/tests/src/Functional/DevelElementInfoTest.php new file mode 100644 index 000000000..57e66045f --- /dev/null +++ b/web/modules/contrib/devel/tests/src/Functional/DevelElementInfoTest.php @@ -0,0 +1,135 @@ +drupalPlaceBlock('system_menu_block:devel'); + $this->drupalPlaceBlock('page_title_block'); + $this->drupalLogin($this->develUser); + } + + /** + * Tests element info menu link. + */ + public function testElementInfoMenuLink() { + $this->drupalPlaceBlock('system_menu_block:devel'); + // Ensures that the element info link is present on the devel menu and that + // it points to the correct page. + $this->drupalGet(''); + $this->clickLink('Element Info'); + $this->assertSession()->statusCodeEquals(200); + $this->assertSession()->addressEquals('/devel/elements'); + $this->assertSession()->pageTextContains('Element Info'); + } + + /** + * Tests element list page. + */ + public function testElementList() { + $this->drupalGet('/devel/elements'); + $this->assertSession()->statusCodeEquals(200); + $this->assertSession()->pageTextContains('Element Info'); + + $page = $this->getSession()->getPage(); + + // Ensures that the element list table is found. + $table = $page->find('css', 'table.devel-element-list'); + $this->assertNotNull($table); + + // Ensures that the expected table headers are found. + $headers = $table->findAll('css', 'thead th'); + $this->assertEquals(4, count($headers)); + + $expected_headers = ['Name', 'Provider', 'Class', 'Operations']; + $actual_headers = array_map(function (NodeElement $element) { + return $element->getText(); + }, $headers); + $this->assertSame($expected_headers, $actual_headers); + + // Tests the presence of some (arbitrarily chosen) elements in the table. + $expected_elements = [ + 'button' => [ + 'class' => 'Drupal\Core\Render\Element\Button', + 'provider' => 'core', + ], + 'form' => [ + 'class' => 'Drupal\Core\Render\Element\Form', + 'provider' => 'core', + ], + 'html' => [ + 'class' => 'Drupal\Core\Render\Element\Html', + 'provider' => 'core', + ], + ]; + + foreach ($expected_elements as $element_name => $element) { + $row = $table->find('css', sprintf('tbody tr:contains("%s")', $element_name)); + $this->assertNotNull($row); + + /* @var $cells \Behat\Mink\Element\NodeElement[] */ + $cells = $row->findAll('css', 'td'); + $this->assertEquals(4, count($cells)); + + $cell = $cells[0]; + $this->assertEquals($element_name, $cell->getText()); + $this->assertTrue($cell->hasClass('table-filter-text-source')); + + $cell = $cells[1]; + $this->assertEquals($element['provider'], $cell->getText()); + $this->assertTrue($cell->hasClass('table-filter-text-source')); + + $cell = $cells[2]; + $this->assertEquals($element['class'], $cell->getText()); + $this->assertTrue($cell->hasClass('table-filter-text-source')); + + $cell = $cells[3]; + $actual_href = $cell->findLink('Devel')->getAttribute('href'); + $expected_href = Url::fromRoute('devel.elements_page.detail', ['element_name' => $element_name])->toString(); + $this->assertEquals($expected_href, $actual_href); + } + + // Ensures that the page is accessible only to the users with the adequate + // permissions. + $this->drupalLogout(); + $this->drupalGet('devel/elements'); + $this->assertSession()->statusCodeEquals(403); + } + + /** + * Tests element detail page. + */ + public function testElementDetail() { + $element_name = 'button'; + + // Ensures that the page works as expected. + $this->drupalGet("/devel/elements/$element_name"); + $this->assertSession()->statusCodeEquals(200); + $this->assertSession()->pageTextContains("Element $element_name"); + + // Ensures that the page returns a 404 error if the requested element is + // not defined. + $this->drupalGet('/devel/elements/not_exists'); + $this->assertSession()->statusCodeEquals(404); + + // Ensures that the page is accessible ony to users with the adequate + // permissions. + $this->drupalLogout(); + $this->drupalGet("/devel/elements/$element_name"); + $this->assertSession()->statusCodeEquals(403); + } + +} diff --git a/web/modules/contrib/devel/tests/src/Functional/DevelEntityTypeInfoTest.php b/web/modules/contrib/devel/tests/src/Functional/DevelEntityTypeInfoTest.php new file mode 100644 index 000000000..98bdea21c --- /dev/null +++ b/web/modules/contrib/devel/tests/src/Functional/DevelEntityTypeInfoTest.php @@ -0,0 +1,170 @@ +drupalPlaceBlock('system_menu_block:devel'); + $this->drupalPlaceBlock('page_title_block'); + $this->drupalLogin($this->develUser); + } + + /** + * Tests entity info menu link. + */ + public function testEntityInfoMenuLink() { + $this->drupalPlaceBlock('system_menu_block:devel'); + // Ensures that the entity type info link is present on the devel menu and + // that it points to the correct page. + $this->drupalGet(''); + $this->clickLink('Entity Info'); + $this->assertSession()->statusCodeEquals(200); + $this->assertSession()->addressEquals('/devel/entity/info'); + $this->assertSession()->pageTextContains('Entity Info'); + } + + /** + * Tests entity type list page. + */ + public function testEntityTypeList() { + $this->drupalGet('/devel/entity/info'); + $this->assertSession()->statusCodeEquals(200); + $this->assertSession()->pageTextContains('Entity Info'); + + $page = $this->getSession()->getPage(); + + // Ensures that the entity type list table is found. + $table = $page->find('css', 'table.devel-entity-type-list'); + $this->assertNotNull($table); + + // Ensures that the expected table headers are found. + $headers = $table->findAll('css', 'thead th'); + $this->assertEquals(5, count($headers)); + + $expected_headers = ['ID', 'Name', 'Provider', 'Class', 'Operations']; + $actual_headers = array_map(function (NodeElement $element) { + return $element->getText(); + }, $headers); + $this->assertSame($expected_headers, $actual_headers); + + // Tests the presence of some (arbitrarily chosen) entity types in the + // table. + $expected_types = [ + 'date_format' => [ + 'name' => 'Date format', + 'class' => 'Drupal\Core\Datetime\Entity\DateFormat', + 'provider' => 'core', + ], + 'block' => [ + 'name' => 'Block', + 'class' => 'Drupal\block\Entity\Block', + 'provider' => 'block', + ], + 'entity_view_mode' => [ + 'name' => 'View mode', + 'class' => 'Drupal\Core\Entity\Entity\EntityViewMode', + 'provider' => 'core', + ], + ]; + + foreach ($expected_types as $entity_type_id => $entity_type) { + $row = $table->find('css', sprintf('tbody tr:contains("%s")', $entity_type_id)); + $this->assertNotNull($row); + + /* @var $cells \Behat\Mink\Element\NodeElement[] */ + $cells = $row->findAll('css', 'td'); + $this->assertEquals(5, count($cells)); + + $cell = $cells[0]; + $this->assertEquals($entity_type_id, $cell->getText()); + $this->assertTrue($cell->hasClass('table-filter-text-source')); + + $cell = $cells[1]; + $this->assertEquals($entity_type['name'], $cell->getText()); + $this->assertTrue($cell->hasClass('table-filter-text-source')); + + $cell = $cells[2]; + $this->assertEquals($entity_type['provider'], $cell->getText()); + $this->assertTrue($cell->hasClass('table-filter-text-source')); + + $cell = $cells[3]; + $this->assertEquals($entity_type['class'], $cell->getText()); + $this->assertTrue($cell->hasClass('table-filter-text-source')); + + $cell = $cells[4]; + $actual_href = $cell->findLink('Devel')->getAttribute('href'); + $expected_href = Url::fromRoute('devel.entity_info_page.detail', ['entity_type_id' => $entity_type_id])->toString(); + $this->assertEquals($expected_href, $actual_href); + + $actual_href = $cell->findLink('Fields')->getAttribute('href'); + $expected_href = Url::fromRoute('devel.entity_info_page.fields', ['entity_type_id' => $entity_type_id])->toString(); + $this->assertEquals($expected_href, $actual_href); + } + + // Ensures that the page is accessible only to the users with the adequate + // permissions. + $this->drupalLogout(); + $this->drupalGet('devel/entity/info'); + $this->assertSession()->statusCodeEquals(403); + } + + /** + * Tests entity type detail page. + */ + public function testEntityTypeDetail() { + $entity_type_id = 'date_format'; + + // Ensures that the page works as expected. + $this->drupalGet("/devel/entity/info/$entity_type_id"); + $this->assertSession()->statusCodeEquals(200); + $this->assertSession()->pageTextContains("Entity type $entity_type_id"); + + // Ensures that the page returns a 404 error if the requested entity type is + // not defined. + $this->drupalGet('/devel/entity/info/not_exists'); + $this->assertSession()->statusCodeEquals(404); + + // Ensures that the page is accessible ony to users with the adequate + // permissions. + $this->drupalLogout(); + $this->drupalGet("/devel/entity/info/$entity_type_id"); + $this->assertSession()->statusCodeEquals(403); + } + + /** + * Tests entity type fields page. + */ + public function testEntityTypeFields() { + $entity_type_id = 'date_format'; + + // Ensures that the page works as expected. + $this->drupalGet("/devel/entity/fields/$entity_type_id"); + $this->assertSession()->statusCodeEquals(200); + $this->assertSession()->pageTextContains("Entity fields $entity_type_id"); + + // Ensures that the page returns a 404 error if the requested entity type is + // not defined. + $this->drupalGet('/devel/entity/fields/not_exists'); + $this->assertSession()->statusCodeEquals(404); + + // Ensures that the page is accessible ony to users with the adequate + // permissions. + $this->drupalLogout(); + $this->drupalGet("/devel/entity/fields/$entity_type_id"); + $this->assertSession()->statusCodeEquals(403); + } + +} diff --git a/web/modules/contrib/devel/tests/src/Functional/DevelErrorHandlerTest.php b/web/modules/contrib/devel/tests/src/Functional/DevelErrorHandlerTest.php new file mode 100644 index 000000000..0d6a3e57c --- /dev/null +++ b/web/modules/contrib/devel/tests/src/Functional/DevelErrorHandlerTest.php @@ -0,0 +1,139 @@ +config('system.logging'); + $config->set('error_level', ERROR_REPORTING_DISPLAY_VERBOSE)->save(); + + $this->drupalLogin($this->adminUser); + + // Ensures that the error handler config is present on the config page and + // by default the standard error handler is selected. + $error_handlers = \Drupal::config('devel.settings')->get('error_handlers'); + $this->assertEquals($error_handlers, [DEVEL_ERROR_HANDLER_STANDARD => DEVEL_ERROR_HANDLER_STANDARD]); + $this->drupalGet('admin/config/development/devel'); + $this->assertTrue($this->assertSession()->optionExists('edit-error-handlers', DEVEL_ERROR_HANDLER_STANDARD)->hasAttribute('selected')); + + // Ensures that selecting the DEVEL_ERROR_HANDLER_NONE option no error + // (raw or message) is shown on the site in case of php errors. + $edit = [ + 'error_handlers[]' => DEVEL_ERROR_HANDLER_NONE, + ]; + $this->drupalPostForm('admin/config/development/devel', $edit, 'Save configuration'); + $this->assertSession()->pageTextContains('The configuration options have been saved.'); + + $error_handlers = \Drupal::config('devel.settings')->get('error_handlers'); + $this->assertEquals($error_handlers, [DEVEL_ERROR_HANDLER_NONE => DEVEL_ERROR_HANDLER_NONE]); + $this->assertTrue($this->assertSession()->optionExists('edit-error-handlers', DEVEL_ERROR_HANDLER_NONE)->hasAttribute('selected')); + + $this->markTestSkipped('Unclear to me what this Error Handler feature does.'); + + $this->clickLink('notice+warning'); + $this->assertSession()->statusCodeEquals(200); + $this->assertSession()->pageTextNotContains($expected_notice); + $this->assertSession()->pageTextNotContains($expected_warning); + $this->assertSession()->elementNotExists('css', $messages_selector); + + // Ensures that selecting the DEVEL_ERROR_HANDLER_BACKTRACE_KINT option a + // backtrace above the rendered page is shown on the site in case of php + // errors. + $edit = [ + 'error_handlers[]' => DEVEL_ERROR_HANDLER_BACKTRACE_KINT, + ]; + $this->drupalPostForm('admin/config/development/devel', $edit, 'Save configuration'); + $this->assertSession()->pageTextContains('The configuration options have been saved.'); + + $error_handlers = \Drupal::config('devel.settings')->get('error_handlers'); + $this->assertEquals($error_handlers, [DEVEL_ERROR_HANDLER_BACKTRACE_KINT => DEVEL_ERROR_HANDLER_BACKTRACE_KINT]); + $this->assertTrue($this->assertSession()->optionExists('edit-error-handlers', DEVEL_ERROR_HANDLER_BACKTRACE_KINT)->hasAttribute('selected')); + + $this->clickLink('notice+warning'); + $this->assertSession()->statusCodeEquals(200); + $this->assertSession()->elementNotExists('css', $messages_selector); + + // Ensures that selecting the DEVEL_ERROR_HANDLER_BACKTRACE_DPM option a + // backtrace in the message area is shown on the site in case of php errors. + $edit = [ + 'error_handlers[]' => DEVEL_ERROR_HANDLER_BACKTRACE_DPM, + ]; + $this->drupalPostForm('admin/config/development/devel', $edit, 'Save configuration'); + $this->assertSession()->pageTextContains('The configuration options have been saved.'); + + $error_handlers = \Drupal::config('devel.settings')->get('error_handlers'); + $this->assertEquals($error_handlers, [DEVEL_ERROR_HANDLER_BACKTRACE_DPM => DEVEL_ERROR_HANDLER_BACKTRACE_DPM]); + $this->assertTrue($this->assertSession()->optionExists('edit-error-handlers', DEVEL_ERROR_HANDLER_BACKTRACE_DPM)->hasAttribute('selected')); + + $this->clickLink('notice+warning'); + $this->assertSession()->statusCodeEquals(200); + $this->assertSession()->elementContains('css', $messages_selector, $expected_notice); + $this->assertSession()->elementContains('css', $messages_selector, $expected_warning); + + // Ensures that when multiple handlers are selected, the output produced by + // every handler is shown on the site in case of php errors. + $edit = [ + 'error_handlers[]' => [ + DEVEL_ERROR_HANDLER_BACKTRACE_KINT => DEVEL_ERROR_HANDLER_BACKTRACE_KINT, + DEVEL_ERROR_HANDLER_BACKTRACE_DPM => DEVEL_ERROR_HANDLER_BACKTRACE_DPM, + ], + ]; + $this->drupalPostForm('admin/config/development/devel', $edit, 'Save configuration'); + $this->assertSession()->pageTextContains('The configuration options have been saved.'); + + $error_handlers = \Drupal::config('devel.settings')->get('error_handlers'); + $this->assertEquals($error_handlers, [ + DEVEL_ERROR_HANDLER_BACKTRACE_KINT => DEVEL_ERROR_HANDLER_BACKTRACE_KINT, + DEVEL_ERROR_HANDLER_BACKTRACE_DPM => DEVEL_ERROR_HANDLER_BACKTRACE_DPM, + ]); + $this->assertTrue($this->assertSession()->optionExists('edit-error-handlers', DEVEL_ERROR_HANDLER_BACKTRACE_KINT)->hasAttribute('selected')); + $this->assertTrue($this->assertSession()->optionExists('edit-error-handlers', DEVEL_ERROR_HANDLER_BACKTRACE_DPM)->hasAttribute('selected')); + + $this->clickLink('notice+warning'); + $this->assertSession()->statusCodeEquals(200); + $this->assertSession()->elementContains('css', $messages_selector, $expected_notice); + $this->assertSession()->elementContains('css', $messages_selector, $expected_warning); + + // Ensures that setting the error reporting to all the output produced by + // handlers is shown on the site in case of php errors. + $config->set('error_level', ERROR_REPORTING_DISPLAY_ALL)->save(); + $this->clickLink('notice+warning'); + $this->assertSession()->statusCodeEquals(200); + $this->assertSession()->elementContains('css', $messages_selector, $expected_notice); + $this->assertSession()->elementContains('css', $messages_selector, $expected_warning); + + // Ensures that setting the error reporting to some the output produced by + // handlers is shown on the site in case of php errors. + $config->set('error_level', ERROR_REPORTING_DISPLAY_SOME)->save(); + $this->clickLink('notice+warning'); + $this->assertSession()->statusCodeEquals(200); + $this->assertSession()->elementContains('css', $messages_selector, $expected_notice); + $this->assertSession()->elementContains('css', $messages_selector, $expected_warning); + + // Ensures that setting the error reporting to none the output produced by + // handlers is not shown on the site in case of php errors. + $config->set('error_level', ERROR_REPORTING_HIDE)->save(); + $this->clickLink('notice+warning'); + $this->assertSession()->statusCodeEquals(200); + $this->assertSession()->pageTextNotContains($expected_notice); + $this->assertSession()->pageTextNotContains($expected_warning); + $this->assertSession()->elementNotExists('css', $messages_selector); + } + +} diff --git a/web/modules/contrib/devel/tests/src/Functional/DevelEventInfoTest.php b/web/modules/contrib/devel/tests/src/Functional/DevelEventInfoTest.php new file mode 100644 index 000000000..1d8959856 --- /dev/null +++ b/web/modules/contrib/devel/tests/src/Functional/DevelEventInfoTest.php @@ -0,0 +1,119 @@ +drupalPlaceBlock('page_title_block'); + $this->drupalLogin($this->develUser); + } + + /** + * Tests event info menu link. + */ + public function testEventsInfoMenuLink() { + $this->drupalPlaceBlock('system_menu_block:devel'); + // Ensures that the events info link is present on the devel menu and that + // it points to the correct page. + $this->drupalGet(''); + $this->clickLink('Events Info'); + $this->assertSession()->statusCodeEquals(200); + $this->assertSession()->addressEquals('/devel/events'); + $this->assertSession()->pageTextContains('Events'); + } + + /** + * Tests event info page. + */ + public function testEventList() { + /** @var \Symfony\Component\EventDispatcher\EventDispatcherInterface $event_dispatcher */ + $event_dispatcher = \Drupal::service('event_dispatcher'); + + $this->drupalGet('/devel/events'); + $this->assertSession()->statusCodeEquals(200); + $this->assertSession()->pageTextContains('Events'); + + $page = $this->getSession()->getPage(); + + // Ensures that the event table is found. + $table = $page->find('css', 'table.devel-event-list'); + $this->assertNotNull($table); + + // Ensures that the expected table headers are found. + /* @var $headers \Behat\Mink\Element\NodeElement[] */ + $headers = $table->findAll('css', 'thead th'); + $this->assertEquals(3, count($headers)); + + $expected_headers = ['Event Name', 'Callable', 'Priority']; + $actual_headers = array_map(function ($element) { + return $element->getText(); + }, $headers); + $this->assertSame($expected_headers, $actual_headers); + + // Ensures that all the events are listed in the table. + $events = $event_dispatcher->getListeners(); + $event_header_row = $table->findAll('css', 'tbody tr th.devel-event-name-header'); + $this->assertEquals(count($events), count($event_header_row)); + + // Tests the presence of some (arbitrarily chosen) events and related + // listeners in the table. The event items are tested dynamically so no + // test failures are expected if listeners change. + $expected_events = [ + 'config.delete', + 'kernel.request', + 'routing.route_alter', + ]; + + foreach ($expected_events as $event_name) { + $listeners = $event_dispatcher->getListeners($event_name); + + // Ensures that the event header is present in the table. + $event_header_row = $table->findAll('css', sprintf('tbody tr th:contains("%s")', $event_name)); + $this->assertNotNull($event_header_row); + $this->assertEquals(1, count($event_header_row)); + + // Ensures that all the event listener are listed in the table. + /* @var $event_rows \Behat\Mink\Element\NodeElement[] */ + $event_rows = $table->findAll('css', sprintf('tbody tr:contains("%s")', $event_name)); + // Remove the header row. + array_shift($event_rows); + $this->assertEquals(count($listeners), count($event_rows)); + + foreach ($listeners as $index => $listener) { + /* @var $cells \Behat\Mink\Element\NodeElement[] */ + $cells = $event_rows[$index]->findAll('css', 'td'); + $this->assertEquals(3, count($cells)); + + $cell_event_name = $cells[0]; + $this->assertEquals($event_name, $cell_event_name->getText()); + $this->assertTrue($cell_event_name->hasClass('table-filter-text-source')); + $this->assertTrue($cell_event_name->hasClass('visually-hidden')); + + $cell_callable = $cells[1]; + is_callable($listener, TRUE, $callable_name); + $this->assertEquals($callable_name, $cell_callable->getText()); + + $cell_methods = $cells[2]; + $priority = $event_dispatcher->getListenerPriority($event_name, $listener); + $this->assertEquals($priority, $cell_methods->getText()); + } + } + + // Ensures that the page is accessible only to the users with the adequate + // permissions. + $this->drupalLogout(); + $this->drupalGet('devel/events'); + $this->assertSession()->statusCodeEquals(403); + } + +} diff --git a/web/modules/contrib/devel/tests/src/Functional/DevelLayoutInfoTest.php b/web/modules/contrib/devel/tests/src/Functional/DevelLayoutInfoTest.php new file mode 100644 index 000000000..49332f313 --- /dev/null +++ b/web/modules/contrib/devel/tests/src/Functional/DevelLayoutInfoTest.php @@ -0,0 +1,140 @@ +drupalPlaceBlock('page_title_block'); + $this->drupalLogin($this->develUser); + } + + /** + * Tests layout info menu link. + */ + public function testLayoutsInfoMenuLink() { + $this->drupalPlaceBlock('system_menu_block:devel'); + // Ensures that the layout info link is present on the devel menu and that + // it points to the correct page. + $this->drupalGet(''); + $this->clickLink('Layouts Info'); + $this->assertSession()->statusCodeEquals(200); + $this->assertSession()->addressEquals('/devel/layouts'); + $this->assertSession()->pageTextContains('Layout'); + } + + /** + * Tests layout info page. + */ + public function testLayoutList() { + $this->drupalGet('/devel/layouts'); + $this->assertSession()->statusCodeEquals(200); + $this->assertSession()->pageTextContains('Layouts'); + + $page = $this->getSession()->getPage(); + + // Ensures that the layout table is found. + $table = $page->find('css', 'table.devel-layout-list'); + $this->assertNotNull($table); + + // Ensures that the expected table headers are found. + /* @var $headers \Behat\Mink\Element\NodeElement[] */ + $headers = $table->findAll('css', 'thead th'); + $this->assertEquals(6, count($headers)); + + $expected_headers = [ + 'Icon', + 'Label', + 'Description', + 'Category', + 'Regions', + 'Provider', + ]; + $actual_headers = array_map(function ($element) { + return $element->getText(); + }, $headers); + $this->assertSame($expected_headers, $actual_headers); + + // Ensures that all the layouts are listed in the table. + $layout_manager = \Drupal::service('plugin.manager.core.layout'); + $layouts = $layout_manager->getDefinitions(); + $table_rows = $table->findAll('css', 'tbody tr'); + $this->assertEquals(count($layouts), count($table_rows)); + + $index = 0; + foreach ($layouts as $layout) { + $cells = $table_rows[$index]->findAll('css', 'td'); + $this->assertEquals(6, count($cells)); + + $cell_layout_icon = $cells[0]; + if (empty($layout->getIconPath())) { + // @todo test that the icon path image is set correctly + } + else { + $this->assertNull($cell_layout_icon->getText()); + } + + $cell_layout_label = $cells[1]; + $this->assertEquals($cell_layout_label->getText(), $layout->getLabel()); + + $cell_layout_description = $cells[2]; + $this->assertEquals($cell_layout_description->getText(), $layout->getDescription()); + + $cell_layout_category = $cells[3]; + $this->assertEquals($cell_layout_category->getText(), $layout->getCategory()); + + $cell_layout_regions = $cells[4]; + $this->assertEquals($cell_layout_regions->getText(), implode(', ', $layout->getRegionLabels())); + + $cell_layout_provider = $cells[5]; + $this->assertEquals($cell_layout_provider->getText(), $layout->getProvider()); + + $index++; + } + + // Ensures that the page is accessible only to the users with the adequate + // permissions. + $this->drupalLogout(); + $this->drupalGet('devel/layouts'); + $this->assertSession()->statusCodeEquals(403); + } + + /** + * Tests the dependency with layout_discovery module. + */ + public function testLayoutDiscoveryDependency() { + $this->container->get('module_installer')->uninstall(['layout_discovery']); + $this->drupalPlaceBlock('system_menu_block:devel'); + + // Ensures that the layout info link is not present on the devel menu. + $this->drupalGet(''); + $this->assertSession()->linkNotExists('Layouts Info'); + + // Ensures that the layouts info page is not available. + $this->drupalGet('/devel/layouts'); + $this->assertSession()->statusCodeEquals(404); + + // Check a few other devel pages to verify devel module stil works. + $this->drupalGet('/devel/events'); + $this->assertSession()->statusCodeEquals(200); + $this->drupalGet('devel/routes'); + $this->assertSession()->statusCodeEquals(200); + $this->drupalGet('/devel/container/service'); + $this->assertSession()->statusCodeEquals(200); + } + +} diff --git a/web/modules/contrib/devel/tests/src/Functional/DevelMenuLinksTest.php b/web/modules/contrib/devel/tests/src/Functional/DevelMenuLinksTest.php new file mode 100644 index 000000000..b2de463c9 --- /dev/null +++ b/web/modules/contrib/devel/tests/src/Functional/DevelMenuLinksTest.php @@ -0,0 +1,93 @@ +drupalPlaceBlock('system_menu_block:devel'); + $this->drupalPlaceBlock('page_title_block'); + $this->drupalLogin($this->adminUser); + } + + /** + * Tests CSFR protected links. + */ + public function testCsrfProtectedLinks() { + // Ensure CSRF link are not accessible directly. + $this->drupalGet('devel/run-cron'); + $this->assertSession()->statusCodeEquals(403); + $this->drupalGet('devel/cache/clear'); + $this->assertSession()->statusCodeEquals(403); + + // Ensure clear cache link works properly. + $this->assertSession()->linkExists('Cache clear'); + $this->clickLink('Cache clear'); + $this->assertSession()->pageTextContains('Cache cleared.'); + + // Ensure run cron link works properly. + $this->assertSession()->linkExists('Run cron'); + $this->clickLink('Run cron'); + $this->assertSession()->pageTextContains('Cron ran successfully.'); + + // Ensure CSRF protected links work properly after change session. + $this->drupalLogout(); + $this->drupalLogin($this->adminUser); + + $this->assertSession()->linkExists('Cache clear'); + $this->clickLink('Cache clear'); + $this->assertSession()->pageTextContains('Cache cleared.'); + + $this->assertSession()->linkExists('Run cron'); + $this->clickLink('Run cron'); + $this->assertSession()->pageTextContains('Cron ran successfully.'); + } + + /** + * Tests redirect destination links. + */ + public function testRedirectDestinationLinks() { + // By default, in the testing profile, front page is the user canonical URI. + // For better testing do not use the default frontpage. + $url = Url::fromRoute('devel.simple_page'); + $destination = Url::fromRoute('devel.simple_page', [], ['absolute' => FALSE]); + + $this->drupalGet($url); + $this->assertSession()->linkExists('Reinstall Modules'); + $this->clickLink('Reinstall Modules'); + $this->assertSession()->addressEquals('devel/reinstall', ['query' => ['destination' => $destination->toString()]]); + + $this->drupalGet($url); + $this->assertSession()->linkExists('Rebuild Menu'); + $this->clickLink('Rebuild Menu'); + $this->assertSession()->addressEquals('devel/menu/reset', ['query' => ['destination' => $destination->toString()]]); + + $this->drupalGet($url); + $this->assertSession()->linkExists('Cache clear'); + $this->clickLink('Cache clear'); + $this->assertSession()->pageTextContains('Cache cleared.'); + $this->assertSession()->addressEquals($url); + + $this->drupalGet($url); + $this->assertSession()->linkExists('Run cron'); + $this->clickLink('Run cron'); + $this->assertSession()->pageTextContains('Cron ran successfully.'); + $this->assertSession()->addressEquals($url); + + } + +} diff --git a/web/modules/contrib/devel/tests/src/Functional/DevelModulesReinstallTest.php b/web/modules/contrib/devel/tests/src/Functional/DevelModulesReinstallTest.php new file mode 100644 index 000000000..9a9a12cff --- /dev/null +++ b/web/modules/contrib/devel/tests/src/Functional/DevelModulesReinstallTest.php @@ -0,0 +1,50 @@ +drupalLogin($this->adminUser); + } + + /** + * Reinstall modules. + */ + public function testDevelReinstallModules() { + // Minimal profile enables only dblog, block and node. + $modules = ['dblog', 'block']; + + // Needed for compare correctly the message. + sort($modules); + + $this->drupalGet('devel/reinstall'); + + // Prepare field data in an associative array. + $edit = []; + foreach ($modules as $module) { + $edit["reinstall[$module]"] = TRUE; + } + + $this->drupalPostForm('devel/reinstall', $edit, 'Reinstall'); + $this->assertSession()->pageTextContains('Uninstalled and installed: ' . implode(', ', $modules) . '.'); + + } + +} diff --git a/web/modules/contrib/devel/tests/src/Functional/DevelRequirementsTest.php b/web/modules/contrib/devel/tests/src/Functional/DevelRequirementsTest.php new file mode 100644 index 000000000..dc1852224 --- /dev/null +++ b/web/modules/contrib/devel/tests/src/Functional/DevelRequirementsTest.php @@ -0,0 +1,25 @@ +drupalLogin($this->adminUser); + + $this->drupalGet('admin/reports/status'); + $this->assertSession()->statusCodeEquals(200); + + $this->assertSession()->pageTextContains('Devel module enabled'); + $this->assertSession()->pageTextContains('The Devel module provides access to internal debugging information; therefore it\'s recommended to disable this module on sites in production.'); + } + +} diff --git a/web/modules/contrib/devel/tests/src/Functional/DevelRouteInfoTest.php b/web/modules/contrib/devel/tests/src/Functional/DevelRouteInfoTest.php new file mode 100644 index 000000000..15c35e3d6 --- /dev/null +++ b/web/modules/contrib/devel/tests/src/Functional/DevelRouteInfoTest.php @@ -0,0 +1,181 @@ +drupalPlaceBlock('system_menu_block:devel'); + $this->drupalPlaceBlock('page_title_block'); + $this->drupalLogin($this->develUser); + } + + /** + * Tests routes info. + */ + public function testRouteList() { + // Ensures that the routes info link is present on the devel menu and that + // it points to the correct page. + $this->drupalGet(''); + $this->clickLink('Routes Info'); + $this->assertSession()->statusCodeEquals(200); + $this->assertSession()->addressEquals('/devel/routes'); + $this->assertSession()->pageTextContains('Routes'); + + $page = $this->getSession()->getPage(); + + // Ensures that the expected table headers are found. + /* @var $headers \Behat\Mink\Element\NodeElement[] */ + $headers = $page->findAll('css', 'table.devel-route-list thead th'); + $this->assertEquals(4, count($headers)); + + $expected_items = ['Route Name', 'Path', 'Allowed Methods', 'Operations']; + foreach ($headers as $key => $element) { + $this->assertSame($element->getText(), $expected_items[$key]); + } + + // Ensures that all the routes are listed in the table. + $routes = \Drupal::service('router.route_provider')->getAllRoutes(); + $rows = $page->findAll('css', 'table.devel-route-list tbody tr'); + $this->assertEquals(count($routes), count($rows)); + + // Tests the presence of some (arbitrarily chosen) routes in the table. + $expected_routes = [ + '' => [ + 'path' => '/', + 'methods' => ['GET', 'POST'], + 'dynamic' => FALSE, + ], + 'user.login' => [ + 'path' => '/user/login', + 'methods' => ['GET', 'POST'], + 'dynamic' => FALSE, + ], + 'entity.user.canonical' => [ + 'path' => '/user/{user}', + 'methods' => ['GET', 'POST'], + 'dynamic' => TRUE, + ], + 'entity.user.devel_load' => [ + 'path' => '/devel/user/{user}', + 'methods' => ['ANY'], + 'dynamic' => TRUE, + ], + ]; + + foreach ($expected_routes as $route_name => $expected) { + $row = $page->find('css', sprintf('table.devel-route-list tbody tr:contains("%s")', $route_name)); + $this->assertNotNull($row); + + /* @var $cells \Behat\Mink\Element\NodeElement[] */ + $cells = $row->findAll('css', 'td'); + $this->assertEquals(4, count($cells)); + + $cell_route_name = $cells[0]; + $this->assertEquals($route_name, $cell_route_name->getText()); + $this->assertTrue($cell_route_name->hasClass('table-filter-text-source')); + + $cell_path = $cells[1]; + $this->assertEquals($expected['path'], $cell_path->getText()); + $this->assertTrue($cell_path->hasClass('table-filter-text-source')); + + $cell_methods = $cells[2]; + $this->assertEquals(implode('', $expected['methods']), $cell_methods->getText()); + + $cell_operations = $cells[3]; + $actual_href = $cell_operations->findLink('Devel')->getAttribute('href'); + if ($expected['dynamic']) { + $parameters = ['query' => ['route_name' => $route_name]]; + } + else { + $parameters = ['query' => ['path' => $expected['path']]]; + } + $expected_href = Url::fromRoute('devel.route_info.item', [], $parameters)->toString(); + $this->assertEquals($expected_href, $actual_href); + } + + // Ensures that the page is accessible only to the users with the adequate + // permissions. + $this->drupalLogout(); + $this->drupalGet('devel/routes'); + $this->assertSession()->statusCodeEquals(403); + } + + /** + * Tests route detail page. + */ + public function testRouteDetail() { + $expected_title = 'Route detail'; + $xpath_warning_messages = '//div[@aria-label="Warning message"]'; + + // Ensures that devel route detail link in the menu works properly. + $url = $this->develUser->toUrl(); + $path = '/' . $url->getInternalPath(); + + $this->drupalGet($url); + $this->clickLink('Current route info'); + $this->assertSession()->statusCodeEquals(200); + $this->assertSession()->pageTextContains($expected_title); + $expected_url = Url::fromRoute('devel.route_info.item', [], ['query' => ['path' => $path]]); + $this->assertSession()->addressEquals($expected_url); + $this->assertSession()->elementNotExists('xpath', $xpath_warning_messages); + + // Ensures that devel route detail works properly even when dynamic cache + // is enabled. + $url = Url::fromRoute('devel.simple_page'); + $path = '/' . $url->getInternalPath(); + + $this->drupalGet($url); + $this->clickLink('Current route info'); + $this->assertSession()->statusCodeEquals(200); + $this->assertSession()->pageTextContains($expected_title); + $expected_url = Url::fromRoute('devel.route_info.item', [], ['query' => ['path' => $path]]); + $this->assertSession()->addressEquals($expected_url); + $this->assertSession()->elementNotExists('xpath', $xpath_warning_messages); + + // Ensures that if a non existent path is passed as input, a warning + // message is shown. + $this->drupalGet('devel/routes/item', ['query' => ['path' => '/undefined']]); + $this->assertSession()->statusCodeEquals(200); + $this->assertSession()->pageTextContains($expected_title); + $this->assertSession()->elementExists('xpath', $xpath_warning_messages); + + // Ensures that the route detail page works properly when a valid route + // name input is passed. + $this->drupalGet('devel/routes/item', ['query' => ['route_name' => 'devel.simple_page']]); + $this->assertSession()->statusCodeEquals(200); + $this->assertSession()->pageTextContains($expected_title); + $this->assertSession()->elementNotExists('xpath', $xpath_warning_messages); + + // Ensures that if a non existent route name is passed as input a warning + // message is shown. + $this->drupalGet('devel/routes/item', ['query' => ['route_name' => 'not.exists']]); + $this->assertSession()->statusCodeEquals(200); + $this->assertSession()->pageTextContains($expected_title); + $this->assertSession()->elementExists('xpath', $xpath_warning_messages); + + // Ensures that if no 'path' nor 'name' query string is passed as input, + // devel route detail page does not return errors. + $this->drupalGet('devel/routes/item'); + $this->assertSession()->statusCodeEquals(200); + $this->assertSession()->pageTextContains($expected_title); + + // Ensures that the page is accessible ony to the users with the adequate + // permissions. + $this->drupalLogout(); + $this->drupalGet('devel/routes/item'); + $this->assertSession()->statusCodeEquals(403); + } + +} diff --git a/web/modules/contrib/devel/tests/src/Functional/DevelRouterRebuildTest.php b/web/modules/contrib/devel/tests/src/Functional/DevelRouterRebuildTest.php new file mode 100644 index 000000000..90916291c --- /dev/null +++ b/web/modules/contrib/devel/tests/src/Functional/DevelRouterRebuildTest.php @@ -0,0 +1,36 @@ +set('devel_test_route_rebuild', NULL); + + $this->drupalGet('devel/menu/reset'); + $this->assertSession()->statusCodeEquals(403); + + $this->drupalLogin($this->adminUser); + + $this->drupalGet('devel/menu/reset'); + $this->assertSession()->statusCodeEquals(200); + $this->assertSession()->pageTextContains('Are you sure you want to rebuild the router?'); + $route_rebuild_state = \Drupal::state()->get('devel_test_route_rebuild'); + $this->assertEmpty($route_rebuild_state); + + $this->drupalPostForm('devel/menu/reset', [], 'Rebuild'); + $this->assertSession()->pageTextContains('The router has been rebuilt.'); + $route_rebuild_state = \Drupal::state()->get('devel_test_route_rebuild'); + $this->assertEquals('Router rebuild fired', $route_rebuild_state); + } + +} diff --git a/web/modules/contrib/devel/tests/src/Functional/DevelStateEditorTest.php b/web/modules/contrib/devel/tests/src/Functional/DevelStateEditorTest.php new file mode 100644 index 000000000..1ca799693 --- /dev/null +++ b/web/modules/contrib/devel/tests/src/Functional/DevelStateEditorTest.php @@ -0,0 +1,184 @@ +state = $this->container->get('state'); + $this->drupalPlaceBlock('page_title_block'); + } + + /** + * Tests state editor menu link. + */ + public function testStateEditMenuLink() { + $this->drupalPlaceBlock('system_menu_block:devel'); + $this->drupalLogin($this->develUser); + // Ensures that the state editor link is present on the devel menu and that + // it points to the correct page. + $this->drupalGet(''); + $this->clickLink('State editor'); + $this->assertSession()->statusCodeEquals(200); + $this->assertSession()->addressEquals('/devel/state'); + $this->assertSession()->pageTextContains('State editor'); + } + + /** + * Tests state listing. + */ + public function testStateListing() { + $table_selector = 'table.devel-state-list'; + + // Ensure that state listing page is accessible only by users with the + // adequate permissions. + $this->drupalGet('devel/state'); + $this->assertSession()->statusCodeEquals(403); + + $this->drupalLogin($this->develUser); + $this->drupalGet('devel/state'); + $this->assertSession()->statusCodeEquals(200); + $this->assertSession()->pageTextContains('State editor'); + + // Ensure that the state variables table is visible. + $table = $this->assertSession()->elementExists('css', $table_selector); + + // Ensure that all state variables are listed in the table. + $states = \Drupal::keyValue('state')->getAll(); + $rows = $table->findAll('css', 'tbody tr'); + $this->assertEquals(count($rows), count($states), 'All states are listed in the table.'); + + // Ensure that the added state variables are listed in the table. + $this->state->set('devel.simple', 'Hello!'); + $this->drupalGet('devel/state'); + $table = $this->assertSession()->elementExists('css', $table_selector); + $this->assertSession()->elementExists('css', sprintf('tbody td:contains("%s")', 'devel.simple'), $table); + + // Ensure that the operations column and the actions buttons are not + // available for user without 'administer site configuration' permission. + $headers = $table->findAll('css', 'thead th'); + $this->assertEquals(count($headers), 2, 'Correct number of table header cells found.'); + $this->assertElementsTextEquals($headers, ['Name', 'Value']); + $this->assertSession()->elementNotExists('css', 'ul.dropbutton li a', $table); + + // Ensure that the operations column and the actions buttons are + // available for user with 'administer site configuration' permission. + $this->drupalLogin($this->adminUser); + $this->drupalGet('devel/state'); + + $table = $this->assertSession()->elementExists('css', $table_selector); + $headers = $table->findAll('css', 'thead th'); + $this->assertEquals(count($headers), 3, 'Correct number of table header cells found.'); + $this->assertElementsTextEquals($headers, ['Name', 'Value', 'Operations']); + $this->assertSession()->elementExists('css', 'ul.dropbutton li a', $table); + + // Test that the edit button works properly. + $this->clickLink('Edit'); + $this->assertSession()->statusCodeEquals(200); + } + + /** + * Tests state edit. + */ + public function testStateEdit() { + // Create some state variables for the test. + $this->state->set('devel.simple', 0); + $this->state->set('devel.array', ['devel' => 'value']); + $this->state->set('devel.object', $this->randomObject()); + + // Ensure that state edit form is accessible only by users with the + // adequate permissions. + $this->drupalLogin($this->develUser); + $this->drupalGet('devel/state/edit/devel.simple'); + $this->assertSession()->statusCodeEquals(403); + + $this->drupalLogin($this->adminUser); + + // Ensure that accessing an un-existent state variable cause a warning + // message. + $this->drupalGet('devel/state/edit/devel.unknown'); + $this->assertSession()->pageTextContains(strtr('State @name does not exist in the system.', ['@name' => 'devel.unknown'])); + + // Ensure that state variables that contain simple type can be edited and + // saved. + $this->drupalGet('devel/state/edit/devel.simple'); + $this->assertSession()->statusCodeEquals(200); + $this->assertSession()->pageTextContains(strtr('Edit state variable: @name', ['@name' => 'devel.simple'])); + $input = $this->assertSession()->fieldExists('edit-new-value'); + $this->assertFalse($input->hasAttribute('disabled')); + $button = $this->assertSession()->buttonExists('edit-submit'); + $this->assertFalse($button->hasAttribute('disabled')); + + $edit = ['new_value' => 1]; + $this->drupalPostForm('devel/state/edit/devel.simple', $edit, 'Save'); + $this->assertSession()->pageTextContains(strtr('Variable @name was successfully edited.', ['@name' => 'devel.simple'])); + $this->assertEquals(1, $this->state->get('devel.simple')); + + // Ensure that state variables that contain array can be edited and saved + // and the new value is properly validated. + $this->drupalGet('devel/state/edit/devel.array'); + $this->assertSession()->statusCodeEquals(200); + $this->assertSession()->pageTextContains(strtr('Edit state variable: @name', ['@name' => 'devel.array'])); + $input = $this->assertSession()->fieldExists('edit-new-value'); + $this->assertFalse($input->hasAttribute('disabled')); + $button = $this->assertSession()->buttonExists('edit-submit'); + $this->assertFalse($button->hasAttribute('disabled')); + + // Try to save an invalid yaml input. + $edit = ['new_value' => 'devel: \'value updated']; + $this->drupalPostForm('devel/state/edit/devel.array', $edit, 'Save'); + $this->assertSession()->pageTextContains('Invalid input:'); + + $edit = ['new_value' => 'devel: \'value updated\'']; + $this->drupalPostForm('devel/state/edit/devel.array', $edit, 'Save'); + $this->assertSession()->pageTextContains(strtr('Variable @name was successfully edited.', ['@name' => 'devel.array'])); + $this->assertEquals(['devel' => 'value updated'], $this->state->get('devel.array')); + + // Ensure that state variables that contain objects cannot be edited. + $this->drupalGet('devel/state/edit/devel.object'); + $this->assertSession()->statusCodeEquals(200); + $this->assertSession()->pageTextContains(strtr('Edit state variable: @name', ['@name' => 'devel.object'])); + $this->assertSession()->pageTextContains(strtr('Only simple structures are allowed to be edited. State @name contains objects.', ['@name' => 'devel.object'])); + $this->assertSession()->fieldDisabled('edit-new-value'); + $button = $this->assertSession()->buttonExists('edit-submit'); + $this->assertTrue($button->hasAttribute('disabled')); + + // Ensure that the cancel link works as expected. + $this->clickLink('Cancel'); + $this->assertSession()->addressEquals('devel/state'); + } + + /** + * Checks that the passed in elements have the expected text. + * + * @param \Behat\Mink\Element\NodeElement[] $elements + * The elements for which check the text. + * @param array $expected_elements_text + * The expected text for the passed in elements. + */ + protected function assertElementsTextEquals(array $elements, array $expected_elements_text) { + $actual_text = array_map(function (NodeElement $element) { + return $element->getText(); + }, $elements); + $this->assertSame($expected_elements_text, $actual_text); + } + +} diff --git a/web/modules/contrib/devel/tests/src/Functional/DevelSwitchUserTest.php b/web/modules/contrib/devel/tests/src/Functional/DevelSwitchUserTest.php new file mode 100644 index 000000000..f62cb2704 --- /dev/null +++ b/web/modules/contrib/devel/tests/src/Functional/DevelSwitchUserTest.php @@ -0,0 +1,305 @@ +block = $this->drupalPlaceBlock('devel_switch_user', ['id' => 'switch-user', 'label' => 'Switch Hit']); + + $this->develUser = $this->drupalCreateUser(['access devel information', 'switch users'], 'Devel User Four'); + $this->switchUser = $this->drupalCreateUser(['switch users'], 'Switch User Five'); + $this->webUser = $this->drupalCreateUser([], 'Web User Six'); + } + + /** + * Tests switch user basic functionality. + */ + public function testSwitchUserFunctionality() { + $this->drupalLogin($this->webUser); + + $this->drupalGet(''); + $this->assertSession()->pageTextNotContains($this->block->label()); + + // Ensure that a token is required to switch user. + $this->drupalGet('/devel/switch/' . $this->webUser->getDisplayName()); + $this->assertSession()->statusCodeEquals(403); + + $this->drupalLogin($this->develUser); + + $this->drupalGet(''); + $this->assertSession()->pageTextContains($this->block->label(), 'Block title was found.'); + + // Ensure that if name in not passed the controller returns access denied. + $this->drupalGet('/devel/switch'); + $this->assertSession()->statusCodeEquals(403); + + // Ensure that a token is required to switch user. + $this->drupalGet('/devel/switch/' . $this->switchUser->getDisplayName()); + $this->assertSession()->statusCodeEquals(403); + + // Switch to another user account. + $this->drupalGet('/user/' . $this->switchUser->id()); + $this->clickLink($this->switchUser->getDisplayName()); + $this->assertSessionByUid($this->switchUser->id()); + $this->assertNoSessionByUid($this->develUser->id()); + + // Switch back to initial account. + $this->clickLink($this->develUser->getDisplayName()); + $this->assertNoSessionByUid($this->switchUser->id()); + $this->assertSessionByUid($this->develUser->id()); + + // Use the search form to switch to another account. + $edit = ['userid' => $this->switchUser->getDisplayName()]; + $this->drupalPostForm(NULL, $edit, 'Switch'); + $this->assertSessionByUid($this->switchUser->id()); + $this->assertNoSessionByUid($this->develUser->id()); + } + + /** + * Tests the switch user block configuration. + */ + public function testSwitchUserBlockConfiguration() { + $anonymous = \Drupal::config('user.settings')->get('anonymous'); + + // Create some users for the test. + for ($i = 0; $i < 12; $i++) { + $this->drupalCreateUser(); + } + + $this->drupalLogin($this->develUser); + + $this->drupalGet(''); + $this->assertSession()->pageTextContains($this->block->label(), 'Block title was found.'); + + // Ensure that block default configuration is effectively used. The block + // default configuration is the following: + // - list_size : 12. + // - include_anon : FALSE. + // - show_form : TRUE. + $this->assertSwitchUserSearchForm(); + $this->assertSwitchUserListCount(12); + $this->assertSwitchUserListNoContainsUser($anonymous); + + // Ensure that changing the list_size configuration property the number of + // user displayed in the list change. + $this->setBlockConfiguration('list_size', 4); + $this->drupalGet(''); + $this->assertSwitchUserListCount(4); + + // Ensure that changing the include_anon configuration property the + // anonymous user is displayed in the list. + $this->setBlockConfiguration('include_anon', TRUE); + $this->drupalGet(''); + $this->assertSwitchUserListContainsUser($anonymous); + + // Ensure that changing the show_form configuration property the + // form is not displayed. + $this->setBlockConfiguration('show_form', FALSE); + $this->drupalGet(''); + $this->assertSwitchUserNoSearchForm(); + } + + /** + * Test the user list items. + */ + public function testSwitchUserListItems() { + $anonymous = \Drupal::config('user.settings')->get('anonymous'); + + $this->setBlockConfiguration('list_size', 2); + + // Login as web user so we are sure that this account is prioritized + // in the list if not enough users with 'switch users' permission are + // present. + $this->drupalLogin($this->webUser); + + $this->drupalLogin($this->develUser); + $this->drupalGet(''); + + // Ensure that users with 'switch users' permission are prioritized. + $this->assertSwitchUserListCount(2); + $this->assertSwitchUserListContainsUser($this->develUser->getDisplayName()); + $this->assertSwitchUserListContainsUser($this->switchUser->getDisplayName()); + + // Ensure that blocked users are not shown in the list. + $this->switchUser->set('status', 0)->save(); + $this->drupalGet(''); + $this->assertSwitchUserListCount(2); + $this->assertSwitchUserListContainsUser($this->develUser->getDisplayName()); + $this->assertSwitchUserListContainsUser($this->webUser->getDisplayName()); + $this->assertSwitchUserListNoContainsUser($this->switchUser->getDisplayName()); + + // Ensure that anonymous user are prioritized if include_anon is set to + // true. + $this->setBlockConfiguration('include_anon', TRUE); + $this->drupalGet(''); + $this->assertSwitchUserListCount(2); + $this->assertSwitchUserListContainsUser($this->develUser->getDisplayName()); + $this->assertSwitchUserListContainsUser($anonymous); + + // Ensure that the switch user block works properly even if no prioritized + // users are found (special handling for user 1). + $this->drupalLogout(); + $this->develUser->delete(); + + $this->drupalLogin($this->rootUser); + $this->drupalGet(''); + $this->assertSwitchUserListCount(2); + // Removed assertion on rootUser which causes random test failures. + // @todo Adjust the tests when user 1 option is completed. + // @see https://www.drupal.org/project/devel/issues/3097047 + // @see https://www.drupal.org/project/devel/issues/3114264 + $this->assertSwitchUserListContainsUser($anonymous); + + // Ensure that the switch user block works properly even if no roles have + // the 'switch users' permission associated (special handling for user 1). + $roles = user_roles(TRUE, 'switch users'); + \Drupal::entityTypeManager()->getStorage('user_role')->delete($roles); + + $this->drupalGet(''); + $this->assertSwitchUserListCount(2); + // Removed assertion on rootUser which causes random test failures. + // @todo Adjust the tests when user 1 option is completed. + // @see https://www.drupal.org/project/devel/issues/3097047 + // @see https://www.drupal.org/project/devel/issues/3114264 + $this->assertSwitchUserListContainsUser($anonymous); + } + + /** + * Helper function for verify the number of items shown in the user list. + * + * @param int $number + * The expected numer of items. + */ + public function assertSwitchUserListCount($number) { + $result = $this->xpath('//div[@id=:block]//ul/li/a', [':block' => 'block-switch-user']); + $this->assertTrue(count($result) == $number, 'The number of users shown in switch user is correct.'); + } + + /** + * Helper function for verify if the user list contains a username. + * + * @param string $username + * The username to check. + */ + public function assertSwitchUserListContainsUser($username) { + $result = $this->xpath('//div[@id=:block]//ul/li/a[normalize-space()=:user]', [':block' => 'block-switch-user', ':user' => $username]); + $this->assertTrue(count($result) > 0, new FormattableMarkup('User "%user" is included in the switch user list.', ['%user' => $username])); + } + + /** + * Helper function for verify if the user list not contains a username. + * + * @param string $username + * The username to check. + */ + public function assertSwitchUserListNoContainsUser($username) { + $result = $this->xpath('//div[@id=:block]//ul/li/a[normalize-space()=:user]', [':block' => 'block-switch-user', ':user' => $username]); + $this->assertTrue(count($result) == 0, new FormattableMarkup('User "%user" is not included in the switch user list.', ['%user' => $username])); + } + + /** + * Helper function for verify if the search form is shown. + */ + public function assertSwitchUserSearchForm() { + $result = $this->xpath('//div[@id=:block]//form[contains(@class, :form)]', [':block' => 'block-switch-user', ':form' => 'devel-switchuser-form']); + $this->assertTrue(count($result) > 0, 'The search form is shown.'); + } + + /** + * Helper function for verify if the search form is not shown. + */ + public function assertSwitchUserNoSearchForm() { + $result = $this->xpath('//div[@id=:block]//form[contains(@class, :form)]', [':block' => 'block-switch-user', ':form' => 'devel-switchuser-form']); + $this->assertTrue(count($result) == 0, 'The search form is not shown.'); + } + + /** + * Protected helper method to set the test block's configuration. + */ + protected function setBlockConfiguration($key, $value) { + $block = $this->block->getPlugin(); + $block->setConfigurationValue($key, $value); + $this->block->save(); + } + + /** + * Asserts that there is a session for a given user ID. + * + * Based off masquarade module. + * + * @param int $uid + * The user ID for which to find a session record. + * + * @TODO find a cleaner way to do this check. + */ + protected function assertSessionByUid($uid) { + $query = \Drupal::database()->select('sessions'); + $query->fields('sessions', ['uid']); + $query->condition('uid', $uid); + $result = $query->execute()->fetchAll(); + // Check that we have some results. + $this->assertNotEmpty($result, sprintf('No session found for uid %s', $uid)); + // If there is more than one session, then that must be unexpected. + $this->assertTrue(count($result) == 1, sprintf('Found more than one session for uid %s', $uid)); + } + + /** + * Asserts that no session exists for a given uid. + * + * Based off masquarade module. + * + * @param int $uid + * The user ID to assert. + * + * @TODO find a cleaner way to do this check. + */ + protected function assertNoSessionByUid($uid) { + $query = \Drupal::database()->select('sessions'); + $query->fields('sessions', ['uid']); + $query->condition('uid', $uid); + $result = $query->execute()->fetchAll(); + $this->assertTrue(empty($result), "No session for uid $uid found."); + } + +} diff --git a/web/modules/contrib/devel/tests/src/Functional/DevelToolbarTest.php b/web/modules/contrib/devel/tests/src/Functional/DevelToolbarTest.php new file mode 100644 index 000000000..0860cb746 --- /dev/null +++ b/web/modules/contrib/devel/tests/src/Functional/DevelToolbarTest.php @@ -0,0 +1,268 @@ +drupalPlaceBlock('local_tasks_block'); + $this->drupalPlaceBlock('page_title_block'); + + $this->develUser = $this->drupalCreateUser([ + 'administer site configuration', + 'access devel information', + 'access toolbar', + ]); + $this->toolbarUser = $this->drupalCreateUser([ + 'access toolbar', + ]); + } + + /** + * Tests configuration form. + */ + public function testConfigurationForm() { + // Ensures that the page is accessible only to users with the adequate + // permissions. + $this->drupalGet('admin/config/development/devel/toolbar'); + $this->assertSession()->statusCodeEquals(403); + + // Ensures that the config page is accessible for users with the adequate + // permissions and the Devel toolbar local task and content are shown. + $this->drupalLogin($this->develUser); + $this->drupalGet('admin/config/development/devel/toolbar'); + $this->assertSession()->statusCodeEquals(200); + $this->assertSession() + ->elementExists('xpath', '//h2[text()="Primary tabs"]/following-sibling::ul//a[contains(text(), "Toolbar Settings")]'); + $this->assertSession()->elementExists('xpath', '//fieldset[@id="edit-toolbar-items--wrapper"]'); + $this->assertSession()->pageTextContains('Devel Toolbar Settings'); + + // Ensures and that all devel menu links are listed in the configuration + // page. + foreach ($this->getMenuLinkInfos() as $link) { + $this->assertSession()->fieldExists(sprintf('toolbar_items[%s]', $link['id'])); + } + + // Ensures and that the default configuration items are selected by + // default. + foreach ($this->defaultToolbarItems as $item) { + $this->assertSession()->checkboxChecked(sprintf('toolbar_items[%s]', $item)); + } + + // Ensures that the configuration save works as expected. + $edit = [ + 'toolbar_items[devel.event_info]' => 'devel.event_info', + 'toolbar_items[devel.theme_registry]' => 'devel.theme_registry', + ]; + $this->drupalPostForm('admin/config/development/devel/toolbar', $edit, 'Save configuration'); + $this->assertSession()->pageTextContains('The configuration options have been saved.'); + + $expected_items = array_merge($this->defaultToolbarItems, ['devel.event_info', 'devel.theme_registry']); + sort($expected_items); + $config_items = \Drupal::config('devel.toolbar.settings')->get('toolbar_items'); + sort($config_items); + + $this->assertEquals($expected_items, $config_items); + } + + /** + * Tests cache metadata headers. + */ + public function testCacheHeaders() { + // Disable user toolbar tab so we can test properly if the devel toolbar + // implementation interferes with the page cacheability. + \Drupal::service('module_installer')->install(['toolbar_disable_user_toolbar']); + + // The menu is not loaded for users without the adequate permission, + // so no cache tags for configuration are added. + $this->drupalLogin($this->toolbarUser); + $this->assertSession()->responseHeaderNotContains('X-Drupal-Cache-Tags', 'config:devel.toolbar.settings'); + $this->assertSession()->responseHeaderNotContains('X-Drupal-Cache-Tags', 'config:system.menu.devel'); + + // Make sure that the configuration cache tags are present for users with + // the adequate permission. + $this->drupalLogin($this->develUser); + $this->assertSession()->responseHeaderContains('X-Drupal-Cache-Tags', 'config:devel.toolbar.settings'); + $this->assertSession()->responseHeaderContains('X-Drupal-Cache-Tags', 'config:system.menu.devel'); + + // The Devel toolbar implementation should not interfere with the page + // cacheability, so you expect a MISS value in the X-Drupal-Dynamic-Cache + // header the first time. + $this->assertSession()->responseHeaderContains('X-Drupal-Dynamic-Cache', 'MISS'); + + // Triggers a page reload and verify that the page is served from the + // cache. + $this->drupalGet(''); + $this->assertSession()->responseHeaderContains('X-Drupal-Dynamic-Cache', 'HIT'); + } + + /** + * Tests toolbar integration. + */ + public function testToolbarIntegration() { + $library_css_url = 'css/devel.toolbar.css'; + $toolbar_selector = '#toolbar-bar .toolbar-tab'; + $toolbar_tab_selector = '#toolbar-bar .toolbar-tab a.toolbar-icon-devel'; + $toolbar_tray_selector = '#toolbar-bar .toolbar-tab #toolbar-item-devel-tray'; + + // Ensures that devel toolbar item is accessible only for user with the + // adequate permissions. + $this->drupalGet(''); + $this->assertSession()->responseNotContains($library_css_url); + $this->assertSession()->elementNotExists('css', $toolbar_selector); + $this->assertSession()->elementNotExists('css', $toolbar_tab_selector); + + $this->drupalLogin($this->toolbarUser); + $this->assertSession()->responseNotContains($library_css_url); + $this->assertSession()->elementExists('css', $toolbar_selector); + $this->assertSession()->elementNotExists('css', $toolbar_tab_selector); + + $this->drupalLogin($this->develUser); + $this->assertSession()->responseContains($library_css_url); + $this->assertSession()->elementExists('css', $toolbar_selector); + $this->assertSession()->elementExists('css', $toolbar_tab_selector); + $this->assertSession()->elementTextContains('css', $toolbar_tab_selector, 'Devel'); + + // Ensures that the configure link in the toolbar is present and point to + // the correct page. + $this->clickLink('Configure'); + $this->assertSession()->addressEquals('admin/config/development/devel/toolbar'); + + // Ensures that the toolbar tray contains the all the menu links. To the + // links not marked as always visible will be assigned a css class that + // allow to hide they when the toolbar has horizontal orientation. + $this->drupalGet(''); + $toolbar_tray = $this->assertSession()->elementExists('css', $toolbar_tray_selector); + + $devel_menu_items = $this->getMenuLinkInfos(); + $toolbar_items = $toolbar_tray->findAll('css', 'ul.toolbar-menu a'); + $this->assertCount(count($devel_menu_items), $toolbar_items); + + foreach ($devel_menu_items as $link) { + $item_selector = sprintf('ul.toolbar-menu a:contains("%s")', $link['title']); + $item = $this->assertSession()->elementExists('css', $item_selector, $toolbar_tray); + // Only test the url up to the ? as the destination and token parameters + // will vary and are not checkable. + $this->assertEquals(strtok($link['url'], '?'), strtok($item->getAttribute('href'), '?')); + + $not_visible = !in_array($link['id'], $this->defaultToolbarItems); + $this->assertTrue($not_visible === $item->hasClass('toolbar-horizontal-item-hidden')); + } + + // Ensures that changing the toolbar settings configuration the changes are + // immediately visible. + $saved_items = $this->config('devel.toolbar.settings')->get('toolbar_items'); + $saved_items[] = 'devel.event_info'; + + $this->config('devel.toolbar.settings') + ->set('toolbar_items', $saved_items) + ->save(); + + $this->drupalGet(''); + $toolbar_tray = $this->assertSession()->elementExists('css', $toolbar_tray_selector); + $item = $this->assertSession()->elementExists('css', sprintf('ul.toolbar-menu a:contains("%s")', 'Events Info'), $toolbar_tray); + $this->assertFalse($item->hasClass('toolbar-horizontal-item-hidden')); + + // Ensures that disabling a menu link it will not more shown in the toolbar + // and that the changes are immediately visible. + $menu_link_manager = \Drupal::service('plugin.manager.menu.link'); + $menu_link_manager->updateDefinition('devel.event_info', ['enabled' => FALSE]); + + $this->drupalGet(''); + $toolbar_tray = $this->assertSession()->elementExists('css', $toolbar_tray_selector); + $this->assertSession()->elementNotExists('css', sprintf('ul.toolbar-menu a:contains("%s")', 'Events Info'), $toolbar_tray); + } + + /** + * Tests devel when toolbar module is not installed. + */ + public function testToolbarModuleNotInstalled() { + // Ensures that when toolbar module is not installed all works properly. + \Drupal::service('module_installer')->uninstall(['toolbar']); + + $this->drupalLogin($this->develUser); + + // Toolbar settings page should respond with 404. + $this->drupalGet('admin/config/development/devel/toolbar'); + $this->assertSession()->statusCodeEquals(404); + + // Primary local task should not contain toolbar tab. + $this->drupalGet('admin/config/development/devel'); + $this->assertSession()->statusCodeEquals(200); + $this->assertSession()->elementNotExists('xpath', '//a[contains(text(), "Toolbar Settings")]'); + + // Toolbar setting config and devel menu cache tags sholud not present. + $this->drupalGet(''); + $this->assertSession()->responseHeaderNotContains('X-Drupal-Cache-Tags', 'config:devel.toolbar.settings'); + $this->assertSession()->responseHeaderNotContains('X-Drupal-Cache-Tags', 'config:system.menu.devel'); + } + + /** + * Helper function for retrieve the menu link informations. + * + * @return array + * An array containing the menu link informations. + */ + protected function getMenuLinkInfos() { + $parameters = new MenuTreeParameters(); + $parameters->onlyEnabledLinks()->setTopLevelOnly(); + $tree = \Drupal::menuTree()->load('devel', $parameters); + + $links = []; + foreach ($tree as $element) { + $links[] = [ + 'id' => $element->link->getPluginId(), + 'title' => $element->link->getTitle(), + 'url' => $element->link->getUrlObject()->toString(), + ]; + } + return $links; + } + +} diff --git a/web/modules/contrib/devel/tests/src/Functional/DevelWebAssertHelper.php b/web/modules/contrib/devel/tests/src/Functional/DevelWebAssertHelper.php new file mode 100644 index 000000000..866074633 --- /dev/null +++ b/web/modules/contrib/devel/tests/src/Functional/DevelWebAssertHelper.php @@ -0,0 +1,34 @@ +xpath('//h2[text()="' . $tab_label . '"]/following-sibling::ul//a'); + $this->assertNotEmpty($elements, 'Local tasks not found.'); + foreach ($routes as $index => $route_info) { + list($route_name, $route_parameters) = $route_info; + $expected = Url::fromRoute($route_name, $route_parameters)->toString(); + $this->assertEquals($expected, $elements[$index]->getAttribute('href')); + } + } + +} diff --git a/web/modules/contrib/devel/tests/src/Kernel/DevelDumperTestTrait.php b/web/modules/contrib/devel/tests/src/Kernel/DevelDumperTestTrait.php new file mode 100644 index 000000000..56026da79 --- /dev/null +++ b/web/modules/contrib/devel/tests/src/Kernel/DevelDumperTestTrait.php @@ -0,0 +1,133 @@ +getDumperExportDump($data, $name); + $this->assertEquals(rtrim($dump), $output, $message); + } + + /** + * Asserts that a haystack contains the dump export output. + * + * Use \Drupal\devel\DevelDumperManager::export(). + * + * @param string $haystack + * The string that contains the dump output to test. + * @param mixed $data + * The variable to dump. + * @param string $name + * (optional) The label to output before variable, defaults to NULL. + * @param string $message + * (optional) A message to display with the assertion. + */ + public function assertContainsDumpExport($haystack, $data, $name = NULL, $message = '') { + // As at 18.04.2020 assertContainsDumpExport() is not actually used in any + // devel tests in any current code branch. + $output = $this->getDumperExportDump($data, $name); + $this->assertStringContainsString($output, (string) $haystack, $message); + } + + /** + * Assertion for ensure dump content. + * + * Asserts that the string passed in input is equals to the string + * representation of a variable obtained dumping the data. + * + * Use \Drupal\devel\DevelDumperManager::dump(). + * + * @param string $dump + * The string that contains the dump output to test. + * @param mixed $data + * The variable to dump. + * @param string $name + * (optional) The label to output before variable, defaults to NULL. + * @param string $message + * (optional) A message to display with the assertion. + */ + public function assertDumpEquals($dump, $data, $name = NULL, $message = '') { + $output = $this->getDumperDump($data, $name); + $this->assertEquals(rtrim($dump), $output, $message); + } + + /** + * Asserts that a haystack contains the dump output. + * + * Use \Drupal\devel\DevelDumperManager::dump(). + * + * @param string $haystack + * The string that contains the dump output to test. + * @param mixed $data + * The variable to dump. + * @param string $name + * (optional) The label to output before variable, defaults to NULL. + * @param string $message + * (optional) A message to display with the assertion. + */ + public function assertContainsDump($haystack, $data, $name = NULL, $message = '') { + $output = $this->getDumperDump($data, $name); + $this->assertStringContainsString($output, (string) $haystack, $message); + } + + /** + * Returns a string representation of a variable. + * + * @param mixed $input + * The variable to dump. + * @param string $name + * (optional) The label to output before variable, defaults to NULL. + * + * @return string + * String representation of a variable. + * + * @see \Drupal\devel\DevelDumperManager::export() + */ + private function getDumperExportDump($input, $name = NULL) { + $output = \Drupal::service('devel.dumper')->export($input, $name); + return rtrim($output); + } + + /** + * Returns a string representation of a variable. + * + * @param mixed $input + * The variable to dump. + * @param string $name + * (optional) The label to output before variable, defaults to NULL. + * + * @return string + * String representation of a variable. + * + * @see \Drupal\devel\DevelDumperManager::dump() + */ + private function getDumperDump($input, $name = NULL) { + ob_start(); + \Drupal::service('devel.dumper')->dump($input, $name); + $output = ob_get_contents(); + ob_end_clean(); + return rtrim($output); + } + +} diff --git a/web/modules/contrib/devel/tests/src/Kernel/DevelEnforcedDependenciesTest.php b/web/modules/contrib/devel/tests/src/Kernel/DevelEnforcedDependenciesTest.php new file mode 100644 index 000000000..4a73e7bca --- /dev/null +++ b/web/modules/contrib/devel/tests/src/Kernel/DevelEnforcedDependenciesTest.php @@ -0,0 +1,80 @@ +installEntitySchema('user'); + $this->installConfig('devel'); + // For uninstall to work. + $this->installSchema('user', ['users_data']); + } + + /** + * Tests devel menu enforced dependencies. + */ + public function testMenuEnforcedDependencies() { + /* @var \Drupal\Core\Config\ConfigManagerInterface $config_manager */ + $config_manager = $this->container->get('config.manager'); + + // Ensure that the Devel menu has explicit enforced dependencies on devel + // module. + $menu = Menu::load('devel'); + $this->assertEquals(['enforced' => ['module' => ['devel']]], $menu->get('dependencies')); + + // Creates an instance of devel menu block so you can test if enforced + // dependencies work properly with it. + $block_id = strtolower($this->randomMachineName(8)); + + $block = Block::create([ + 'plugin' => 'system_menu_block:devel', + 'region' => 'sidebar_first', + 'id' => $block_id, + 'theme' => $this->config('system.theme')->get('default'), + 'label' => $this->randomMachineName(8), + 'visibility' => [], + 'weight' => 0, + ]); + $block->save(); + + // Ensure that the menu and block instance depend on devel module. + $dependents = $config_manager->findConfigEntityDependents('module', ['devel']); + $this->assertArrayHasKey('system.menu.devel', $dependents); + $this->assertArrayHasKey('block.block.' . $block_id, $dependents); + + $this->container->get('module_installer')->uninstall(['devel']); + + // Ensure that the menu and block instance are deleted when the dependency + // is uninstalled. + $this->assertNull(Menu::load('devel')); + $this->assertNull(Block::load($block_id)); + + // Ensure that no config entities depend on devel once uninstalled. + $dependents = $config_manager->findConfigEntityDependents('module', ['devel']); + $this->assertArrayNotHasKey('system.menu.devel', $dependents); + $this->assertArrayNotHasKey('block.block.' . $block_id, $dependents); + } + +} diff --git a/web/modules/contrib/devel/tests/src/Kernel/DevelMailLogTest.php b/web/modules/contrib/devel/tests/src/Kernel/DevelMailLogTest.php new file mode 100644 index 000000000..26a4d577f --- /dev/null +++ b/web/modules/contrib/devel/tests/src/Kernel/DevelMailLogTest.php @@ -0,0 +1,201 @@ +installConfig(['system', 'devel']); + + // Configure system.site mail settings. + $this->config('system.site')->set('mail', 'devel-test@example.com')->save(); + + $this->mailManager = $this->container->get('plugin.manager.mail'); + } + + /** + * Tests devel_mail_log plugin as default mail backend. + */ + public function testDevelMailLogDefaultBackend() { + // Configure devel_mail_log as default mail backends. + $this->setDevelMailLogAsDefaultBackend(); + + // Ensures that devel_mail_log is the default mail plugin . + $mail_backend = $this->mailManager->getInstance(['module' => 'default', 'key' => 'default']); + $this->assertInstanceOf(DevelMailLog::class, $mail_backend); + + $mail_backend = $this->mailManager->getInstance(['module' => 'somemodule', 'key' => 'default']); + $this->assertInstanceOf(DevelMailLog::class, $mail_backend); + } + + /** + * Tests devel_mail_log plugin with multiple mail backend. + */ + public function testDevelMailLogMultipleBackend() { + // Configure test_mail_collector as default mail backend. + $this->config('system.mail') + ->set('interface.default', 'test_mail_collector') + ->save(); + + // Configure devel_mail_log as a module-specific mail backend. + $this->config('system.mail') + ->set('interface.somemodule', 'devel_mail_log') + ->save(); + + // Ensures that devel_mail_log is not the default mail plugin. + $mail_backend = $this->mailManager->getInstance(['module' => 'default', 'key' => 'default']); + $this->assertInstanceOf(TestMailCollector::class, $mail_backend); + + // Ensures that devel_mail_log is used as mail backend only for the + // specified module. + $mail_backend = $this->mailManager->getInstance(['module' => 'somemodule', 'key' => 'default']); + $this->assertInstanceOf(DevelMailLog::class, $mail_backend); + } + + /** + * Tests devel_mail_log default settings. + */ + public function testDevelMailDefaultSettings() { + $config = \Drupal::config('devel.settings'); + $this->assertEquals('temporary://devel-mails', $config->get('debug_mail_directory')); + $this->assertEquals('%to-%subject-%datetime.mail.txt', $config->get('debug_mail_file_format')); + } + + /** + * Tests devel mail log output. + */ + public function testDevelMailLogOutput() { + $config = \Drupal::config('devel.settings'); + + // Parameters used for send the email. + $mail = [ + 'module' => 'devel_test', + 'key' => 'devel_mail_log', + 'to' => 'drupal@example.com', + 'reply' => 'replyto@example.com', + 'lang' => \Drupal::languageManager()->getCurrentLanguage(), + ]; + + // Parameters used for compose the email in devel_test module. + // @see devel_test_mail() + $params = [ + 'subject' => 'Devel mail log subject', + 'body' => 'Devel mail log body', + 'headers' => [ + 'from' => 'postmaster@example.com', + 'additional' => [ + 'X-stupid' => 'dumb', + ], + ], + ]; + + // Configure devel_mail_log as default mail backends. + $this->setDevelMailLogAsDefaultBackend(); + + // Changes the default filename pattern removing the dynamic date + // placeholder for a more predictable filename output. + $random = $this->randomMachineName(); + $filename_pattern = '%to-%subject-' . $random . '.mail.txt'; + $this->config('devel.settings') + ->set('debug_mail_file_format', $filename_pattern) + ->save(); + + $expected_filename = 'drupal@example.com-Devel_mail_log_subject-' . $random . '.mail.txt'; + $expected_output = <<get('debug_mail_directory'); + $expected_file_path = $default_output_directory . '/' . $expected_filename; + + $this->mailManager->mail($mail['module'], $mail['key'], $mail['to'], $mail['lang'], $params, $mail['reply']); + $this->assertFileExists($expected_file_path); + $this->assertStringEqualsFile($expected_file_path, $expected_output); + + // Ensures that even changing the default output directory devel_mail_log + // works as expected. + $changed_output_directory = 'temporary://my-folder'; + $expected_file_path = $changed_output_directory . '/' . $expected_filename; + $this->config('devel.settings') + ->set('debug_mail_directory', $changed_output_directory) + ->save(); + + $result = $this->mailManager->mail($mail['module'], $mail['key'], $mail['to'], $mail['lang'], $params, $mail['reply']); + $this->assertSame(TRUE, $result['result']); + $this->assertFileExists($expected_file_path); + $this->assertStringEqualsFile($expected_file_path, $expected_output); + + // Ensures that if the default output directory is a public directory it + // will be protected by adding an .htaccess. + $public_output_directory = 'public://my-folder'; + $expected_file_path = $public_output_directory . '/' . $expected_filename; + $this->config('devel.settings') + ->set('debug_mail_directory', $public_output_directory) + ->save(); + + $this->mailManager->mail($mail['module'], $mail['key'], $mail['to'], $mail['lang'], $params, $mail['reply']); + $this->assertFileExists($expected_file_path); + $this->assertStringEqualsFile($expected_file_path, $expected_output); + $this->assertFileExists($public_output_directory . '/.htaccess'); + } + + /** + * Configure devel_mail_log as default mail backend. + */ + private function setDevelMailLogAsDefaultBackend() { + // TODO can this be avoided? + // KernelTestBase enforce the usage of 'test_mail_collector' plugin for + // collect the mails. Since we need to test devel mail plugin we manually + // configure the mail implementation to use 'devel_mail_log'. + $GLOBALS['config']['system.mail']['interface']['default'] = 'devel_mail_log'; + + // Configure devel_mail_log as default mail backend. + $this->config('system.mail') + ->set('interface.default', 'devel_mail_log') + ->save(); + } + +} diff --git a/web/modules/contrib/devel/tests/src/Kernel/DevelQueryDebugTest.php b/web/modules/contrib/devel/tests/src/Kernel/DevelQueryDebugTest.php new file mode 100644 index 000000000..5b6ee18d0 --- /dev/null +++ b/web/modules/contrib/devel/tests/src/Kernel/DevelQueryDebugTest.php @@ -0,0 +1,136 @@ +installSchema('system', 'sequences'); + $this->installConfig(['system', 'devel']); + $this->installEntitySchema('user'); + + $devel_role = Role::create([ + 'id' => 'admin', + 'permissions' => ['access devel information'], + ]); + $devel_role->save(); + + $this->develUser = User::create([ + 'name' => $this->randomMachineName(), + 'roles' => [$devel_role->id()], + ]); + $this->develUser->save(); + } + + /** + * Tests devel_query_debug_alter() for select queries. + */ + public function testSelectQueryDebugTag() { + // Clear the messages stack. + $this->getDrupalMessages(); + + // Ensures that no debug message is shown to user without the adequate + // permissions. + $query = \Drupal::database()->select('users', 'u'); + $query->fields('u', ['uid']); + $query->addTag('debug'); + $query->execute(); + + $messages = $this->getDrupalMessages(); + $this->assertEmpty($messages); + + // Ensures that the SQL debug message is shown to user with the adequate + // permissions. We expect only one status message containing the SQL for + // the debugged query. + \Drupal::currentUser()->setAccount($this->develUser); + $expected_message = "SELECT u.uid AS uid\nFROM\n{users} u"; + + $query = \Drupal::database()->select('users', 'u'); + $query->fields('u', ['uid']); + $query->addTag('debug'); + $query->execute(); + + $messages = $this->getDrupalMessages(); + $this->assertNotEmpty($messages['status']); + $this->assertCount(1, $messages['status']); + $actual_message = strip_tags($messages['status'][0]); + // In Drupal 9 the literals are quoted, but not in Drupal 8. We only need + // the actual content, so remove all quotes from the actual message found. + $actual_message = str_replace(['"', "'"], ['', ''], $actual_message); + $this->assertEquals($expected_message, $actual_message); + } + + /** + * Tests devel_query_debug_alter() for entity queries. + */ + public function testEntityQueryDebugTag() { + // Clear the messages stack. + $this->getDrupalMessages(); + + // Ensures that no debug message is shown to user without the adequate + // permissions. + $query = \Drupal::entityQuery('user'); + $query->addTag('debug'); + $query->execute(); + + $messages = $this->getDrupalMessages(); + $this->assertEmpty($messages); + + // Ensures that the SQL debug message is shown to user with the adequate + // permissions. We expect only one status message containing the SQL for + // the debugged entity query. + \Drupal::currentUser()->setAccount($this->develUser); + $expected_message = "SELECT base_table.uid AS uid, base_table.uid AS base_table_uid\nFROM\n{users} base_table"; + + $query = \Drupal::entityQuery('user'); + $query->addTag('debug'); + $query->execute(); + + $messages = $this->getDrupalMessages(); + $this->assertNotEmpty($messages['status']); + $this->assertCount(1, $messages['status']); + $actual_message = strip_tags($messages['status'][0]); + $actual_message = str_replace(['"', "'"], ['', ''], $actual_message); + $this->assertEquals($expected_message, $actual_message); + } + + /** + * Retrieves and removes the drupal messages. + * + * @return array + * The messages + */ + protected function getDrupalMessages() { + return $this->messenger()->deleteAll(); + } + +} diff --git a/web/modules/contrib/devel/tests/src/Kernel/DevelTwigExtensionTest.php b/web/modules/contrib/devel/tests/src/Kernel/DevelTwigExtensionTest.php new file mode 100644 index 000000000..e7d21ecab --- /dev/null +++ b/web/modules/contrib/devel/tests/src/Kernel/DevelTwigExtensionTest.php @@ -0,0 +1,204 @@ +installEntitySchema('user'); + $this->installSchema('system', 'sequences'); + + $devel_role = Role::create([ + 'id' => 'admin', + 'permissions' => ['access devel information'], + ]); + $devel_role->save(); + + $this->develUser = User::create([ + 'name' => $this->randomMachineName(), + 'roles' => [$devel_role->id()], + ]); + $this->develUser->save(); + } + + /** + * {@inheritdoc} + */ + public function register(ContainerBuilder $container) { + parent::register($container); + + $parameters = $container->getParameter('twig.config'); + $parameters['debug'] = TRUE; + $container->setParameter('twig.config', $parameters); + } + + /** + * Tests that Twig extension loads appropriately. + */ + public function testTwigExtensionLoaded() { + $twig_service = \Drupal::service('twig'); + $extension = $twig_service->getExtension(Debug::class); + $this->assertEquals(get_class($extension), Debug::class, 'Debug Extension loaded successfully.'); + } + + /** + * Tests that the Twig dump functions are registered properly. + */ + public function testDumpFunctionsRegistered() { + /* @var \Twig_SimpleFunction[] $functions */ + $functions = \Drupal::service('twig')->getFunctions(); + + $dump_functions = ['devel_dump', 'kpr']; + $message_functions = ['devel_message', 'dpm', 'dsm']; + $registered_functions = $dump_functions + $message_functions; + + foreach ($registered_functions as $name) { + $function = $functions[$name]; + $this->assertTrue($function instanceof \Twig_SimpleFunction); + $this->assertEquals($function->getName(), $name); + $this->assertTrue($function->needsContext()); + $this->assertTrue($function->needsEnvironment()); + $this->assertTrue($function->isVariadic()); + + is_callable($function->getCallable(), TRUE, $callable); + if (in_array($name, $dump_functions)) { + $this->assertEquals($callable, 'Drupal\devel\Twig\Extension\Debug::dump'); + } + else { + $this->assertEquals($callable, 'Drupal\devel\Twig\Extension\Debug::message'); + } + } + } + + /** + * Tests that the Twig function for XDebug integration is registered properly. + */ + public function testXdebugIntegrationFunctionsRegistered() { + /* @var \Twig_SimpleFunction $function */ + $function = \Drupal::service('twig')->getFunction('devel_breakpoint'); + $this->assertTrue($function instanceof \Twig_SimpleFunction); + $this->assertEquals($function->getName(), 'devel_breakpoint'); + $this->assertTrue($function->needsContext()); + $this->assertTrue($function->needsEnvironment()); + $this->assertTrue($function->isVariadic()); + is_callable($function->getCallable(), TRUE, $callable); + $this->assertEquals($callable, 'Drupal\devel\Twig\Extension\Debug::breakpoint'); + } + + /** + * Tests that the Twig extension's dump functions produce the expected output. + */ + public function testDumpFunctions() { + $template = 'test-with-context {{ twig_string }} {{ twig_array.first }} {{ twig_array.second }}{{ devel_dump() }}'; + $expected_template_output = 'test-with-context context! first value second value'; + + $context = [ + 'twig_string' => 'context!', + 'twig_array' => [ + 'first' => 'first value', + 'second' => 'second value', + ], + 'twig_object' => new \stdClass(), + ]; + + /* @var \Drupal\Core\Template\TwigEnvironment $environment */ + $environment = \Drupal::service('twig'); + + // Ensures that the twig extension does nothing if the current + // user has not the adequate permission. + $this->assertTrue($environment->isDebug()); + $this->assertEquals($environment->renderInline($template, $context), $expected_template_output); + + \Drupal::currentUser()->setAccount($this->develUser); + + // Ensures that if no argument is passed to the function the twig context is + // dumped. + $output = (string) $environment->renderInline($template, $context); + $this->assertStringContainsString($expected_template_output, $output, 'When no argument passed'); + $this->assertContainsDump($output, $context, 'Twig context'); + + // Ensures that if an argument is passed to the function it is dumped. + $template = 'test-with-context {{ twig_string }} {{ twig_array.first }} {{ twig_array.second }}{{ devel_dump(twig_array) }}'; + $output = (string) $environment->renderInline($template, $context); + $this->assertStringContainsString($expected_template_output, $output, 'When one argument is passed'); + $this->assertContainsDump($output, $context['twig_array']); + + // Ensures that if more than one argument is passed the function works + // properly and every argument is dumped separately. + $template = 'test-with-context {{ twig_string }} {{ twig_array.first }} {{ twig_array.second }}{{ devel_dump(twig_string, twig_array.first, twig_array, twig_object) }}'; + $output = (string) $environment->renderInline($template, $context); + $this->assertStringContainsString($expected_template_output, $output, 'When multiple arguments are passed'); + $this->assertContainsDump($output, $context['twig_string']); + $this->assertContainsDump($output, $context['twig_array']['first']); + $this->assertContainsDump($output, $context['twig_array']); + $this->assertContainsDump($output, $context['twig_object']); + + // Clear messages. + $this->messenger()->deleteAll(); + + $retrieve_message = function ($messages, $index) { + return isset($messages['status'][$index]) ? (string) $messages['status'][$index] : NULL; + }; + + // Ensures that if no argument is passed to the function the twig context is + // dumped. + $template = 'test-with-context {{ twig_string }} {{ twig_array.first }} {{ twig_array.second }}{{ devel_message() }}'; + $output = (string) $environment->renderInline($template, $context); + $this->assertStringContainsString($expected_template_output, $output, 'When no argument passed'); + $messages = \Drupal::messenger()->deleteAll(); + $this->assertDumpExportEquals($retrieve_message($messages, 0), $context, 'Twig context'); + + // Ensures that if an argument is passed to the function it is dumped. + $template = 'test-with-context {{ twig_string }} {{ twig_array.first }} {{ twig_array.second }}{{ devel_message(twig_array) }}'; + $output = (string) $environment->renderInline($template, $context); + $this->assertStringContainsString($expected_template_output, $output, 'When one argument is passed'); + $messages = $this->messenger()->deleteAll(); + $this->assertDumpExportEquals($retrieve_message($messages, 0), $context['twig_array']); + + // Ensures that if more than one argument is passed to the function works + // properly and every argument is dumped separately. + $template = 'test-with-context {{ twig_string }} {{ twig_array.first }} {{ twig_array.second }}{{ devel_message(twig_string, twig_array.first, twig_array, twig_object) }}'; + $output = (string) $environment->renderInline($template, $context); + $this->assertStringContainsString($expected_template_output, $output, 'When multiple arguments are passed'); + $messages = $this->messenger()->deleteAll(); + $this->assertDumpExportEquals($retrieve_message($messages, 0), $context['twig_string']); + $this->assertDumpExportEquals($retrieve_message($messages, 1), $context['twig_array']['first']); + $this->assertDumpExportEquals($retrieve_message($messages, 2), $context['twig_array']); + $this->assertDumpExportEquals($retrieve_message($messages, 3), $context['twig_object']); + } + +} diff --git a/web/modules/contrib/devel/tests/src/Unit/DevelClientSideFilterTableTest.php b/web/modules/contrib/devel/tests/src/Unit/DevelClientSideFilterTableTest.php new file mode 100644 index 000000000..b2f6b94c6 --- /dev/null +++ b/web/modules/contrib/devel/tests/src/Unit/DevelClientSideFilterTableTest.php @@ -0,0 +1,204 @@ +getStringTranslationStub(); + + $expected_info = [ + '#filter_label' => $translation->translate('Search'), + '#filter_placeholder' => $translation->translate('Search'), + '#filter_description' => $translation->translate('Search'), + '#header' => [], + '#rows' => [], + '#empty' => '', + '#sticky' => FALSE, + '#responsive' => TRUE, + '#attributes' => [], + '#pre_render' => [ + [ClientSideFilterTable::class, 'preRenderTable'], + ], + ]; + + $table = new ClientSideFilterTable([], 'test', 'test'); + $table->setStringTranslation($translation); + $this->assertEquals($expected_info, $table->getInfo()); + } + + /** + * @covers ::preRenderTable + * @dataProvider providerPreRenderTable + */ + public function testPreRenderTable($element, $expected) { + $result = ClientSideFilterTable::preRenderTable($element); + $this->assertEquals($expected, $result); + } + + /** + * Data provider for preRenderHtmlTag test. + */ + public function providerPreRenderTable() { + $data = []; + + $t = $this->getStringTranslationStub(); + + $actual = [ + '#type' => 'devel_table_filter', + '#filter_label' => $t->translate('Label 1'), + '#filter_placeholder' => $t->translate('Placeholder 1'), + '#filter_description' => $t->translate('Description 1'), + '#header' => [], + '#rows' => [], + '#empty' => $t->translate('Empty 1'), + '#responsive' => TRUE, + '#sticky' => TRUE, + '#attributes' => [ + 'class' => ['devel-a-list'], + ], + ]; + + $expected = []; + $expected['#attached']['library'][] = 'devel/devel-table-filter'; + $expected['filters'] = [ + '#type' => 'container', + '#weight' => -1, + '#attributes' => ['class' => ['table-filter', 'js-show']], + 'name' => [ + '#type' => 'search', + '#size' => 30, + '#title' => $t->translate('Label 1'), + '#placeholder' => $t->translate('Placeholder 1'), + '#attributes' => [ + 'class' => ['table-filter-text'], + 'data-table' => ".js-devel-table-filter", + 'autocomplete' => 'off', + 'title' => $t->translate('Description 1'), + ], + ], + ]; + $expected['table'] = [ + '#type' => 'table', + '#header' => [], + '#rows' => [], + '#empty' => $t->translate('Empty 1'), + '#responsive' => TRUE, + '#sticky' => TRUE, + '#attributes' => [ + 'class' => [ + 'devel-a-list', + 'js-devel-table-filter', + 'devel-table-filter', + ], + ], + ]; + + $data[] = [$actual, $expected]; + + $headers = ['Test1', 'Test2', 'Test3', 'Test4', 'Test5']; + + $actual = [ + '#type' => 'devel_table_filter', + '#filter_label' => $t->translate('Label 2'), + '#filter_placeholder' => $t->translate('Placeholder 2'), + '#filter_description' => $t->translate('Description 2'), + '#header' => $headers, + '#rows' => [ + [ + ['data' => 'test1', 'filter' => TRUE], + ['data' => 'test2', 'filter' => TRUE, 'class' => ['test2']], + ['data' => 'test3', 'class' => ['test3']], + ['test4'], + [ + 'data' => 'test5', + 'filter' => TRUE, + 'class' => ['devel-event-name-header'], + 'colspan' => '3', + 'header' => TRUE, + ], + ], + ], + '#empty' => $t->translate('Empty 2'), + '#responsive' => FALSE, + '#sticky' => FALSE, + '#attributes' => [ + 'class' => ['devel-some-list'], + ], + ]; + + $expected = []; + $expected['#attached']['library'][] = 'devel/devel-table-filter'; + $expected['filters'] = [ + '#type' => 'container', + '#weight' => -1, + '#attributes' => ['class' => ['table-filter', 'js-show']], + 'name' => [ + '#type' => 'search', + '#size' => 30, + '#title' => $t->translate('Label 2'), + '#placeholder' => $t->translate('Placeholder 2'), + '#attributes' => [ + 'class' => ['table-filter-text'], + 'data-table' => ".js-devel-table-filter--2", + 'autocomplete' => 'off', + 'title' => $t->translate('Description 2'), + ], + ], + ]; + $expected['table'] = [ + '#type' => 'table', + '#header' => $headers, + '#rows' => [ + [ + [ + 'data' => 'test1', + 'filter' => TRUE, + 'class' => ['table-filter-text-source'], + ], + [ + 'data' => 'test2', + 'filter' => TRUE, + 'class' => ['test2', 'table-filter-text-source'], + ], + ['data' => 'test3', 'class' => ['test3']], + ['test4'], + [ + 'data' => 'test5', + 'filter' => TRUE, + 'class' => ['devel-event-name-header', 'table-filter-text-source'], + 'colspan' => '3', + 'header' => TRUE, + ], + ], + ], + '#empty' => $t->translate('Empty 2'), + '#responsive' => FALSE, + '#sticky' => FALSE, + '#attributes' => [ + 'class' => [ + 'devel-some-list', + 'js-devel-table-filter--2', + 'devel-table-filter', + ], + ], + ]; + + $data[] = [$actual, $expected]; + + return $data; + } + +} diff --git a/web/modules/contrib/devel/webprofiler/README.md b/web/modules/contrib/devel/webprofiler/README.md new file mode 100644 index 000000000..69ae48db7 --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/README.md @@ -0,0 +1,86 @@ +[[_TOC_]] + +#### Dependencies +- d3.js: Webprofiler module requires D3 library 3.x (not 4.x) to render data. +- highlight.js: Webprofiler module requires highlight 9.7.x library to syntax highlight collected queries. + +#### Install using Composer (recommended) +If you use Composer to manage dependencies, edit [composer.json](composer.json) as follows: + +1. Run `composer require composer/installers` to ensure that you have the `composer/installers` package installed. This package facilitates the installation of packages into directories other than `/vendor` (e.g. `/libraries`) using Composer. +1. Add the following to the `installer-paths` section of `composer.json`: + ``` + "libraries/{$name}": ["type:drupal-library"], + ``` + When you are using the drupal-composer/drupal-project template add the following instead: + ``` + "web/libraries/{$name}": ["type:drupal-library"], + ``` +1. Add the following to the "repositories" section of `composer.json`: + ``` + { + "type": "package", + "package": { + "name": "d3/d3", + "version": "v3.5.17", + "type": "drupal-library", + "source": { + "url": "https://github.com/d3/d3.git", + "type": "git", + "reference": "tags/v3.5.17" + } + } + }, + { + "type": "package", + "package": { + "name": "highlightjs/highlightjs", + "version": "9.7.0", + "type": "drupal-library", + "source": { + "url": "https://github.com/highlightjs/highlight.js.git", + "type": "git", + "reference": "tags/9.7.0" + } + } + } + ``` +1. Run `composer require d3/d3 highlightjs/highlightjs` - you should find that new directories have been created +under `libraries` + +#### Install manually + +- d3.js: + + - Create a `/libraries/d3/` directory below your Drupal root directory + - Download https://d3js.org/d3.v3.min.js + - Rename it to `/libraries/d3/d3.min.js` + + For further details on how to obtain D3.js, see https://github.com/d3/d3/ + +- highlight.js: + + - Create `/libraries/highlightjs/` directory below your Drupal root directory + - Download the library and CSS from http://highlightjs.org into it + +#### IDE link + +Each class name discovered while profiling (controller class, event class) is specially linked to open the class in +an IDE. You can configure the URLs for these links to work for your IDE. + +- [Sublime text (2 and 3) - macOS](https://github.com/dhoulb/subl) +- Textmate. Use `txmt://open?url=file://@file&line=@line` +- PhpStorm. Use `phpstorm://open?file=@file&line=@line` + +#### Timeline + +It is also possible to collect the time needed to instantiate every single service used in a request. + +Add the following two lines to `settings.php` (or, even better, to `settings.local.php`): + + ``` + $class_loader->addPsr4('Drupal\\webprofiler\\', [ __DIR__ . '/../../modules/contrib/devel/webprofiler/src']); + $settings['container_base_class'] = '\Drupal\webprofiler\DependencyInjection\TraceableContainer'; + ``` + +Check if the path from the Webprofiler module in your `settings.php` file matches the location of the installed Webprofiler module in your project. diff --git a/web/modules/contrib/devel/webprofiler/composer.json b/web/modules/contrib/devel/webprofiler/composer.json new file mode 100644 index 000000000..3c133f9c0 --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/composer.json @@ -0,0 +1,15 @@ +{ + "name": "drupal/webprofiler", + "description": "Drupal Web Profiler.", + "type": "drupal-module", + "authors": [ + { + "name": "Luca Lusso", + "email": "lussoluca@gmail.com", + "homepage": "https://github.com/lussoluca", + "role": "Maintainer" + } + ], + "license": "GPL-2.0-or-later", + "require": {} +} diff --git a/web/modules/contrib/devel/webprofiler/config/install/webprofiler.config.yml b/web/modules/contrib/devel/webprofiler/config/install/webprofiler.config.yml new file mode 100644 index 000000000..5e8127433 --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/config/install/webprofiler.config.yml @@ -0,0 +1,28 @@ +purge_on_cache_clear: true +storage: profiler.database_storage +exclude: "/contextual/*\r\n/toolbar/*\r\n/edit/*\r\n*.js\r\n*.css" +ide_link: "txmt://open?url=file://@file&line=@line" +ide_link_remote: "" +ide_link_local: "" +active_toolbar_items: + devel: devel + assets: assets + blocks: blocks + cache: cache + database: database + drupal_extension: drupal_extension + forms: forms + performance_timing: performance_timing + php_config: php_config + request: request + time: time + user: user + views: views + config: '0' + events: '0' + http: '0' + routing: '0' + services: '0' + state: '0' +query_sort: source +query_highlight: 5 diff --git a/web/modules/contrib/devel/webprofiler/config/schema/webprofiler.schema.yml b/web/modules/contrib/devel/webprofiler/config/schema/webprofiler.schema.yml new file mode 100644 index 000000000..1adc2f166 --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/config/schema/webprofiler.schema.yml @@ -0,0 +1,35 @@ +# Schema for the configuration files of the Webprofiler module. +webprofiler.config: + type: config_object + label: 'Webprofiler configuration' + mapping: + purge_on_cache_clear: + type: boolean + label: 'Purge profiles on cache clear' + storage: + type: string + label: 'Storage implementation' + exclude: + type: string + label: 'Paths to exclude' + active_toolbar_items: + type: sequence + label: 'Active toolbar items' + sequence: + - type: string + label: 'Toolbar item' + ide_link: + type: string + label: 'IDE link' + ide_link_remote: + type: string + label: 'IDE link remote path' + ide_link_local: + type: string + label: 'IDE link local path' + query_sort: + type: string + label: 'Sort query log' + query_highlight: + type: integer + label: 'Slow query highlighting' diff --git a/web/modules/contrib/devel/webprofiler/console.services.yml b/web/modules/contrib/devel/webprofiler/console.services.yml new file mode 100644 index 000000000..fc662e47d --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/console.services.yml @@ -0,0 +1,14 @@ +services: + webprofiler.list: + class: Drupal\webprofiler\Command\ListCommand + tags: + - { name: drupal.command } + lazy: true + webprofiler.export: + class: Drupal\webprofiler\Command\ExportCommand + tags: + - { name: drupal.command } + webprofiler.benchmark: + class: Drupal\webprofiler\Command\BenchmarkCommand + tags: + - { name: drupal.command } diff --git a/web/modules/contrib/devel/webprofiler/console/translations/en/webprofiler.benchmark.yml b/web/modules/contrib/devel/webprofiler/console/translations/en/webprofiler.benchmark.yml new file mode 100644 index 000000000..3f6da3629 --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/console/translations/en/webprofiler.benchmark.yml @@ -0,0 +1,20 @@ +description: Benchmark an url. +arguments: + url: Url to benchmark. +options: + runs: Number of runs. + file: Save results as file. + cache-rebuild: Rebuild cache before start benchmark. +messages: + not_git: Not in a git repository. + error_login: Impossibile to login in the user. +progress: + cache_rebuild: Rebuilding cache... + login: Login user... + get: Http request... + compute_avg: Compute average... + compute_median: Compute median... + compute_95percentile: Compute 95 percentile... + git_hash: Compute GIT hash... + yaml: Generate output... + done: Done. diff --git a/web/modules/contrib/devel/webprofiler/console/translations/en/webprofiler.export.yml b/web/modules/contrib/devel/webprofiler/console/translations/en/webprofiler.export.yml new file mode 100644 index 000000000..e11c8d7dd --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/console/translations/en/webprofiler.export.yml @@ -0,0 +1,15 @@ +description: Exports Webprofiler profile/s to file. +arguments: + id: Profile id. +options: + directory: Destination directory to store exported file/s. +messages: + success: Succesfully exported to %s + exported_count: Exported %s profiles + error_writing: Error writing file %s + error_no_profile: No profile with id %s +progress: + exporting: Exporting profiles... + archive: Create archive... + delete_tmp: Delete temp files... + done: Done. diff --git a/web/modules/contrib/devel/webprofiler/console/translations/en/webprofiler.list.yml b/web/modules/contrib/devel/webprofiler/console/translations/en/webprofiler.list.yml new file mode 100644 index 000000000..c8cb699a3 --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/console/translations/en/webprofiler.list.yml @@ -0,0 +1,14 @@ +description: Lists Webprofiler profiles. +options: + ip: Filter by IP. + url: Filter by URL. + method: Filter by HTTP method. + limit: Limit printed profiles. +rows: + time: D, m/d/Y - H:i:s +header: + token: Token + ip: IP + method: Method + url: URL + time: Time diff --git a/web/modules/contrib/devel/webprofiler/console/translations/es/webprofiler.benchmark.yml b/web/modules/contrib/devel/webprofiler/console/translations/es/webprofiler.benchmark.yml new file mode 100644 index 000000000..8ed902ebb --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/console/translations/es/webprofiler.benchmark.yml @@ -0,0 +1,20 @@ +description: Benchmark una URL. +arguments: + url: Url para efectuar un benchmark. +options: + runs: Número de ejecuciones. + file: Guardar los resultados en un archivo. + cache-rebuild: Reconstruir cache antes de iniciar el benchmark. +messages: + not_git: No en un repositorio git. + error_login: Error ingresando al sistema. +progress: + cache_rebuild: Reconstruyendo cache... + login: Ingresando al sistema... + get: Solicitud Http... + compute_avg: Calculando promedio... + compute_median: Calculando media... + compute_95percentile: Calculando percentile 95... + git_hash: Calculando GIT hash... + yaml: Generando salida... + done: Terminado. diff --git a/web/modules/contrib/devel/webprofiler/console/translations/es/webprofiler.export.yml b/web/modules/contrib/devel/webprofiler/console/translations/es/webprofiler.export.yml new file mode 100644 index 000000000..d7ca92011 --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/console/translations/es/webprofiler.export.yml @@ -0,0 +1,15 @@ +description: Exportar Webprofiler perfil(es) a un archivo. +arguments: + id: Profile id. +options: + directory: Directorio de destiono para almacenar archivo(s) exportados. +messages: + success: Satisfactoriamente exportado en %s + exported_count: Exportados %s perfiles + error_writing: Error escribiendo el archivo %s + error_no_profile: No fue encontrado un perfil con id %s +progress: + exporting: Exportando perfiles... + archive: Creando archivos... + delete_tmp: Borrando archivos temporales... + done: Terminado. diff --git a/web/modules/contrib/devel/webprofiler/console/translations/es/webprofiler.list.yml b/web/modules/contrib/devel/webprofiler/console/translations/es/webprofiler.list.yml new file mode 100644 index 000000000..e4635aef1 --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/console/translations/es/webprofiler.list.yml @@ -0,0 +1,14 @@ +description: Listar perfiles Webprofiler. +options: + ip: Filtrar por IP. + url: Filtrar por URL. + method: Filtrar por método HTTP. + limit: Limitar perfiles a mostrar. +rows: + time: D, d/m/Y - H:i:s +header: + token: Token + ip: IP + method: Método + url: URL + time: Fecha diff --git a/web/modules/contrib/devel/webprofiler/css/app/dashboard.css b/web/modules/contrib/devel/webprofiler/css/app/dashboard.css new file mode 100644 index 000000000..ce74d7d60 --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/css/app/dashboard.css @@ -0,0 +1,852 @@ +/* base themes */ +#webrofiler { + overflow: hidden; + font-size: 1em; + line-height: 1.2; + color: rgba(0, 0, 0, 0.7); + background-color: #ffffff; +} +#webrofiler * { + box-sizing: border-box; +} + +#webprofiler { + /* + + overview example + +
    +
  • + + + + + + + */ + /* + + general element template + + will be implemented in the next dashboard relase + + this HTML will contain the preview of any single card present inside the dashboard + + + +
    +

    + + +
    +
    text +
    text + + */ } +#webprofiler .tabs:after, +#webprofiler .tabs__tabs:after, +#webprofiler .tabs__panels:after, +#webprofiler .tabs__panel:after, +#webprofiler .resume:after, +#webprofiler .collectors:after, +#webprofiler .panel:after, +#webprofiler .panel__toolbar:after, +#webprofiler .panel__container:after, +#webprofiler .panel__expand-header:after, +#webprofiler .panel__expand-content:after, +#webprofiler .app__bar:after, +#webprofiler .app__container:after, +#webprofiler .modal__bar:after, +#webprofiler .modal__content:after { + content: ' '; + display: block; + clear: both; +} +#webprofiler .panel__container, +#webprofiler .ui--red, +#webprofiler .ui--pink, +#webprofiler .ui--purple, +#webprofiler .ui--deepPurple, +#webprofiler .ui--indigo, +#webprofiler .ui--blue, +#webprofiler .ui--lightBlue, +#webprofiler .ui--cyan, +#webprofiler .ui--teal, +#webprofiler .ui--green, +#webprofiler .ui--lightGreen, +#webprofiler .ui--lime, +#webprofiler .ui--yellow, +#webprofiler .ui--amber, +#webprofiler .ui--orange, +#webprofiler .ui--deepOrange, +#webprofiler .ui--brown, +#webprofiler .ui--gray, +#webprofiler .ui--blueGray, +#webprofiler .modal__container { + box-shadow: 0 2px 4px -2px #000000; + margin-bottom: 40px; + border-bottom-right-radius: 2px; + border-bottom-left-radius: 2px; + overflow: hidden; +} +#webprofiler .ui--red .app__title, +#webprofiler .ui--pink .app__title, +#webprofiler .ui--purple .app__title, +#webprofiler .ui--deepPurple .app__title, +#webprofiler .ui--blue .app__title, +#webprofiler .ui--lightBlue .app__title, +#webprofiler .ui--cyan .app__title, +#webprofiler .ui--green .app__title, +#webprofiler .ui--lightGreen .app__title, +#webprofiler .ui--lime .app__title, +#webprofiler .ui--yellow .app__title, +#webprofiler .ui--amber .app__title, +#webprofiler .ui--orange .app__title, +#webprofiler .ui--deepOrange .app__title, +#webprofiler .ui--brown .app__title { + color: white; +} +#webprofiler .ui--indigo .app__title, +#webprofiler .ui--teal .app__title, +#webprofiler .ui--gray .app__title, +#webprofiler .ui--blueGray .app__title, +#webprofiler .modal__title { + color: rgba(0, 0, 0, 0.87); +} +#webprofiler .ui--red .app__main-data, +#webprofiler .ui--pink .app__main-data, +#webprofiler .ui--purple .app__main-data, +#webprofiler .ui--deepPurple .app__main-data, +#webprofiler .ui--indigo .app__main-data, +#webprofiler .ui--blue .app__main-data, +#webprofiler .ui--lightBlue .app__main-data, +#webprofiler .ui--cyan .app__main-data, +#webprofiler .ui--teal .app__main-data, +#webprofiler .ui--green .app__main-data, +#webprofiler .ui--lightGreen .app__main-data, +#webprofiler .ui--lime .app__main-data, +#webprofiler .ui--yellow .app__main-data, +#webprofiler .ui--amber .app__main-data, +#webprofiler .ui--orange .app__main-data, +#webprofiler .ui--deepOrange .app__main-data, +#webprofiler .ui--brown .app__main-data, +#webprofiler .ui--gray .app__main-data, +#webprofiler .ui--blueGray .app__main-data, +#webprofiler .modal__content { + color: rgba(0, 0, 0, 0.7); +} +#webprofiler .ui--red .app__secondary-data, +#webprofiler .ui--pink .app__secondary-data, +#webprofiler .ui--purple .app__secondary-data, +#webprofiler .ui--deepPurple .app__secondary-data, +#webprofiler .ui--indigo .app__secondary-data, +#webprofiler .ui--blue .app__secondary-data, +#webprofiler .ui--lightBlue .app__secondary-data, +#webprofiler .ui--cyan .app__secondary-data, +#webprofiler .ui--teal .app__secondary-data, +#webprofiler .ui--green .app__secondary-data, +#webprofiler .ui--lightGreen .app__secondary-data, +#webprofiler .ui--lime .app__secondary-data, +#webprofiler .ui--yellow .app__secondary-data, +#webprofiler .ui--amber .app__secondary-data, +#webprofiler .ui--orange .app__secondary-data, +#webprofiler .ui--deepOrange .app__secondary-data, +#webprofiler .ui--brown .app__secondary-data, +#webprofiler .ui--gray .app__secondary-data, +#webprofiler .ui--blueGray .app__secondary-data { + color: rgba(0, 0, 0, 0.3); +} +#webprofiler a { + color: #2979ff; +} +#webprofiler a:hover { + color: #2962ff; + text-decoration: none; +} +#webprofiler .list--unstyled { + margin: 0; + list-style-type: none; + padding: 0; +} +#webprofiler .list--unstyled > li { + display: block; + padding: 0 8px; +} +#webprofiler .list--inline { + margin: 0; + list-style-type: none; + padding: 0; +} +#webprofiler .list--inline > li { + display: inline-block; + padding: 8px; +} +#webprofiler .list--level-0 li { + padding: 0 0 0 0; +} +#webprofiler .list--level-1 li { + padding: 0 0 0 8px; +} +#webprofiler .list--level-3 li { + padding: 0 0 0 16px; +} +#webprofiler pre { + padding: 16px; +} +#webprofiler pre, +#webprofiler code { + white-space: normal; + word-break: break-all; + word-break: break-word; + font-size: 14px; + background-color: #f5f5f5; +} +#webprofiler .list-item--bold { + font-weight: bold; +} +#webprofiler code.code--json { + white-space: pre-wrap; +} +#webprofiler thead { + white-space: nowrap; +} +#webprofiler thead th { + background-color: transparent; +} +#webprofiler th { + font-size: 12px; + color: rgba(0, 0, 0, 0.54); + border: none; +} +#webprofiler td { + word-break: break-all; + font-size: 13px; + color: rgba(0, 0, 0, 0.87); +} +#webprofiler tr { + border-bottom: 1px solid rgba(0, 0, 0, 0.18); +} +#webprofiler tr:hover { + background-color: #eeeeee; +} +#webprofiler td, +#webprofiler th { + padding: 16px 24px; + min-height: 46px; + text-transform: none; + vertical-align: top; +} +#webprofiler .table--compact { + table-layout: fixed; +} +#webprofiler .table--compact td, +#webprofiler .table--compact th { + word-spacing: 99em; +} +#webprofiler .table--fixed { + table-layout: fixed; +} +#webprofiler .table--duo th:first-child, +#webprofiler .table--duo td:first-child { + width: 35%; +} +#webprofiler .table--trio th:first-child, +#webprofiler .table--trio td:first-child { + width: 80%; +} +#webprofiler .table--trio th:nth-child(2), +#webprofiler .table--trio td:nth-child(2), +#webprofiler .table--trio th:nth-child(3), +#webprofiler .table--trio td:nth-child(3) { + width: 10%; +} +#webprofiler [class*="button"] { + display: block; + color: #2979ff; + border-bottom-right-radius: 2px; + border-bottom-left-radius: 2px; + border: none; + text-transform: uppercase; + font-size: 16px; + text-align: center; + padding: 16px; + margin: 8px; + cursor: pointer; + min-width: 42px; +} +#webprofiler .button--flat { + background-color: transparent; +} +#webprofiler .button--flat:hover { + background-color: #eeeeee; +} +#webprofiler .button--flat:active, +#webprofiler .button--flat:focus { + background-color: #bdbdbd; +} +#webprofiler .tabs { + position: relative; + margin: -16px 0 16px 0; +} +#webprofiler .tabs a { + display: inline; +} +#webprofiler .tabs__radio { + display: none; +} +#webprofiler .tabs__tabs { + margin: 0 -16px 16px -16px; + text-transform: uppercase; + height: 48px; + background-color: #f5f5f5; +} +#webprofiler .tabs__tabs > li { + padding: 0; +} +#webprofiler .tabs__label { + border-bottom: 5px solid transparent; + text-align: center; + line-height: 48px; + min-width: 160px; + display: inline-block; + margin: -5px 0 0 0; +} +#webprofiler .tabs__panel { + display: none; + width: 100%; +} +#webprofiler .tabs__radio:checked:nth-child(1) ~ .tabs__panels .tabs__panel:nth-child(1) { + display: block; +} +#webprofiler .tabs__radio:checked:nth-child(2) ~ .tabs__panels .tabs__panel:nth-child(2) { + display: block; +} +#webprofiler .tabs__radio:checked:nth-child(3) ~ .tabs__panels .tabs__panel:nth-child(3) { + display: block; +} +#webprofiler .tabs__radio:checked:nth-child(1) ~ .tabs__tabs li:nth-child(1) .tabs__label { + border-bottom-color: #2962ff; +} +#webprofiler .tabs__radio:checked:nth-child(2) ~ .tabs__tabs li:nth-child(2) .tabs__label { + border-bottom-color: #2962ff; +} +#webprofiler .tabs__radio:checked:nth-child(3) ~ .tabs__tabs li:nth-child(3) .tabs__label { + border-bottom-color: #2962ff; +} +#webprofiler .overview { + display: table-cell; + width: 20%; + vertical-align: top; + font-size: 1.2em; + line-height: 2em; + background: #ffffff; +} +#webprofiler .overview__list { + padding: 0; + margin: -1px -1px 0 0; + list-style-type: none; + border-top: 1px solid rgba(0, 0, 0, 0.18); +} +#webprofiler .overview__link { + position: relative; + display: block; + padding: 0 0 0 72px; + border-right: 5px solid transparent; +} +#webprofiler .overview__link:after { + content: ''; + display: block; + position: absolute; + bottom: 0; + right: -5px; + left: 72px; + border-bottom: 1px solid rgba(0, 0, 0, 0.18); +} +#webprofiler .overview__icon { + position: absolute; + top: 16px; + left: 16px; +} +#webprofiler .overview__title { + display: block; + font-size: 16px; + line-height: 22px; + padding-top: 16px; + padding-bottom: 16px; + padding-right: 11px; +} +#webprofiler .overview__subtitle { + display: block; + font-size: 14px; + line-height: 20px; + padding-bottom: 16px; + padding-right: 11px; + margin-top: -16px; +} +#webprofiler .is--selected .overview__link { + color: #000000; + background-color: #f5f5f5; + border-right-color: #2962ff; +} +#webprofiler .overview__link:hover { + color: #000000; + background-color: #82b1ff; +} +#webprofiler .resume { + position: relative; + background-color: #e0e0e0; + border-bottom: 1px solid rgba(0, 0, 0, 0.18); + border-left: 1px solid rgba(0, 0, 0, 0.18); + padding: 16px 16px 0 16px; + margin-left: 20%; +} +#webprofiler .resume__title { + display: block; + font-size: 24px; + line-height: 32px; +} +#webprofiler .resume__subtitle, +#webprofiler .resume__time { + display: block; + font-size: 16px; + line-height: 24px; +} +#webprofiler .resume__time { + margin-bottom: 52px; +} +#webprofiler .resume__button { + position: absolute; + bottom: 0; + right: 16px; +} +#webprofiler .collectors { + display: table; + width: 100%; +} +#webprofiler .details { + display: table-cell; + overflow: hidden; + padding: 16px; + border-left: 1px solid rgba(0, 0, 0, 0.18); +} +#webprofiler .details__title { + border-bottom: 1px solid #ccc; + padding-bottom: 5px; +} +#webprofiler .details svg { + max-width: 100%; +} +#webprofiler [class*='panel__filter--'] { + display: inline-block; + padding: 32px 16px 0 16px; + position: relative; +} +#webprofiler .panel { + margin: 0 0 20px; + padding: 0; + background: #fafafa; + border: none; +} +#webprofiler .panel select, +#webprofiler .panel input[type="text"] { + min-width: 165px; + font-size: 16px; + background-color: #fafafa; + border-color: transparent; + border-width: 0; + border-bottom: 2px solid #ffffff; + padding: 8px 0; + margin-bottom: 8px; +} +#webprofiler .panel select:focus, +#webprofiler .panel input[type="text"]:focus { + outline: none; + border-bottom-color: #2979ff; +} +#webprofiler .panel select:focus + label, +#webprofiler .panel input[type="text"]:focus + label { + color: #2979ff; +} +#webprofiler .panel select { + min-width: 165px; + padding-right: 24px; + border-radius: 0; + background: url(../../images/caret-down.svg) no-repeat 99% 63%; + -moz-appearance: none; +} +#webprofiler .panel select:hover { + box-shadow: none; +} +#webprofiler .panel__title { + font-size: 24px; + line-height: 1; + background-color: #f5f5f5; + text-transform: none; + padding: 16px; + margin: -16px -16px 16px -16px; +} +#webprofiler .panel__toolbar { + background-color: #fafafa; + margin: -16px -16px 16px -16px; +} +#webprofiler .panel__filter-action { + display: inline-block; +} +#webprofiler .panel__filter-label { + display: block; + position: absolute; + top: 0; + margin-top: 16px; +} +#webprofiler .panel__container { + background-color: #ffffff; + position: relative; + padding: 16px; +} +#webprofiler .panel__expand-content { + display: none; +} +#webprofiler .is--open .panel__expand-content { + display: block; +} +#webprofiler .is--hightlighted { + border-right: 10px solid #aa00ff; + padding-right: 6px; +} +#webprofiler .is--hidden { + display: none; +} +#webprofiler .app { + overflow: hidden; + position: relative; + display: block; + cursor: pointer; +} +#webprofiler .app__bar { + padding: 16px; +} +#webprofiler .app__title { + margin: 8px 32px 0 0; + font-weight: normal; +} +#webprofiler .app__icon { + float: right; + height: 32px; + width: 32px; +} +#webprofiler .app__container { + padding: 16px; +} +#webprofiler .app__main-data { + font-size: 24px; + padding-bottom: 16px; +} +#webprofiler .app__secondary-data { + font-size: 14px; + padding-bottom: 16px; + margin-top: -8px; +} +#webprofiler .ui--red .app__bar { + background-color: #ff1744; +} +#webprofiler .ui--red .app__container { + background-color: #ff8a80; +} +#webprofiler .ui--pink .app__bar { + background-color: #f50057; +} +#webprofiler .ui--pink .app__container { + background-color: #ff80ab; +} +#webprofiler .ui--purple .app__bar { + background-color: #d500f9; +} +#webprofiler .ui--purple .app__container { + background-color: #ea80fc; +} +#webprofiler .ui--deepPurple .app__bar { + background-color: #651fff; +} +#webprofiler .ui--deepPurple .app__container { + background-color: #b388ff; +} +#webprofiler .ui--indigo .app__bar { + background-color: #3d5afe; +} +#webprofiler .ui--indigo .app__container { + background-color: #8c9eff; +} +#webprofiler .ui--blue .app__bar { + background-color: #2979ff; +} +#webprofiler .ui--blue .app__container { + background-color: #82b1ff; +} +#webprofiler .ui--lightBlue .app__bar { + background-color: #00b0ff; +} +#webprofiler .ui--lightBlue .app__container { + background-color: #80d8ff; +} +#webprofiler .ui--cyan .app__bar { + background-color: #00e5ff; +} +#webprofiler .ui--cyan .app__container { + background-color: #84ffff; +} +#webprofiler .ui--teal .app__bar { + background-color: #ade9b6; +} +#webprofiler .ui--teal .app__container { + background-color: #a7ffeb; +} +#webprofiler .ui--green .app__bar { + background-color: #00e676; +} +#webprofiler .ui--green .app__container { + background-color: #b9f6ca; +} +#webprofiler .ui--lightGreen .app__bar { + background-color: #76ff03; +} +#webprofiler .ui--lightGreen .app__container { + background-color: #ccff90; +} +#webprofiler .ui--lime .app__bar { + background-color: #c6ff00; +} +#webprofiler .ui--lime .app__container { + background-color: #f4ff81; +} +#webprofiler .ui--yellow .app__bar { + background-color: #ffea00; +} +#webprofiler .ui--yellow .app__container { + background-color: #ffff8d; +} +#webprofiler .ui--amber .app__bar { + background-color: #ffc400; +} +#webprofiler .ui--amber .app__container { + background-color: #ffe57f; +} +#webprofiler .ui--orange .app__bar { + background-color: #ff9100; +} +#webprofiler .ui--orange .app__container { + background-color: #ffd180; +} +#webprofiler .ui--deepOrange .app__bar { + background-color: #ff3d00; +} +#webprofiler .ui--deepOrange .app__container { + background-color: #ff9e80; +} +#webprofiler .ui--brown .app__bar { + background-color: #8d6e63; +} +#webprofiler .ui--brown .app__container { + background-color: #d7ccc8; +} +#webprofiler .ui--gray .app__bar { + background-color: #bdbdbd; +} +#webprofiler .ui--gray .app__container { + background-color: #f5f5f5; +} +#webprofiler .ui--blueGray .app__bar { + background-color: #bdbdbd; +} +#webprofiler .ui--blueGray .app__container { + background-color: #f5f5f5; +} +#webprofiler .loader--fixed { + position: fixed; + right: 0; + top: 50%; + width: 80%; + height: 100px; + zoom: 1.7; + text-align: center; +} +#webprofiler .loader__circle { + animation: rotate 2s linear infinite; + height: 100px; + position: relative; + width: 100px; +} +#webprofiler .loader__path { + stroke-dasharray: 1, 200; + stroke-dashoffset: 0; + animation: dash 1.5s ease-in-out infinite, color 6s ease-in-out infinite; + stroke-linecap: round; +} +@keyframes rotate { + 100% { + transform: rotate(360deg); +} } +@keyframes dash { + 0% { + stroke-dasharray: 1, 200; + stroke-dashoffset: 0; +} + 50% { + stroke-dasharray: 89, 200; + stroke-dashoffset: -35; +} + 100% { + stroke-dasharray: 89, 200; + stroke-dashoffset: -124; +} } +@keyframes color { + 100%, + 0% { + stroke: #ff1744; +} + 40% { + stroke: #2979ff; +} + 66% { + stroke: #00e676; +} + 80%, + 90% { + stroke: #ffea00; +} } +#webprofiler .loader--linear { + height: 3px; + background-color: #ffea00; + position: absolute; + top: 0; + left: 0; + right: 0; +} +#webprofiler .loader__bar { + content: ""; + display: inline; + position: absolute; + width: 0; + height: 100%; + left: 50%; + text-align: center; +} +#webprofiler .loader__bar:nth-child(1) { + background-color: #ff1744; + animation: loading 3s linear infinite; +} +#webprofiler .loader__bar:nth-child(2) { + background-color: #2979ff; + animation: loading 3s linear 1s infinite; +} +#webprofiler .loader__bar:nth-child(3) { + background-color: #00e676; + animation: loading 3s linear 2s infinite; +} +@keyframes loading { + from { + left: 50%; + width: 0; + z-index: 100; +} + 33.3333% { + left: 0; + width: 100%; + z-index: 10; +} + to { + left: 0; + width: 100%; +} } +#webprofiler .modal { + background-color: rgba(0, 0, 0, 0.87); + position: fixed; + top: 0; + bottom: 0; + left: 0; + right: 0; +} +#webprofiler .modal__container { + border-top-right-radius: 2px; + border-top-left-radius: 2px; + box-shadow: 0 2px 4px -2px #000000; + background-color: #ffffff; + position: relative; + width: 33%; + min-width: 350px; + margin: 0 auto; + margin-top: 15%; +} +#webprofiler .modal__bar { + padding: 16px; +} +#webprofiler .modal__content { + padding: 16px; + font-size: 14px; +} +#webprofiler .modal__title { + margin: 8px 32px 0 0; + font-weight: normal; + font-size: 24px; +} +#webprofiler .modal__main-data { + font-size: 24px; + padding-bottom: 16px; +} +#webprofiler .modal textarea { + white-space: normal; + word-break: break-all; + word-break: break-word; + font-size: 14px; + background-color: #f5f5f5; + max-width: 100%; + height: 320px; + width: 100%; + padding: 16px; + box-sizing: border-box; + border: none; +} +#webprofiler .t--heading, +#webprofiler .tabs__radio:checked:nth-child(1) ~ .tabs__tabs li:nth-child(1) .tabs__label, +#webprofiler .tabs__radio:checked:nth-child(2) ~ .tabs__tabs li:nth-child(2) .tabs__label, +#webprofiler .tabs__radio:checked:nth-child(3) ~ .tabs__tabs li:nth-child(3) .tabs__label, +#webprofiler .overview__title, +#webprofiler .resume__title, +#webprofiler .panel select, +#webprofiler .panel input[type="text"], +#webprofiler .panel__title { + color: black; +} +#webprofiler .t--notice, +#webprofiler .tabs__label, +#webprofiler .overview__subtitle, +#webprofiler .resume__subtitle, +#webprofiler .resume__time, +#webprofiler .panel__filter-label { + color: rgba(0, 0, 0, 0.3); +} +#webprofiler .l-card { + width: 32%; + margin-left: 1%; + margin-right: 1%; + float: left; +} +#webprofiler .l-card:nth-child(3n + 1) { + margin-left: 0; +} +#webprofiler .l-card:nth-child(3n) { + margin-right: 0; +} +#webprofiler .l-left { + float: left; +} +#webprofiler .l-right { + float: right; +} +#webprofiler .h--word-broken { + word-break: break-all; +} +#webprofiler .h--word-intact { + word-break: normal; +} + +/*# sourceMappingURL=dashboard.css.map */ diff --git a/web/modules/contrib/devel/webprofiler/css/timeline.css b/web/modules/contrib/devel/webprofiler/css/timeline.css new file mode 100644 index 000000000..2cd6f709e --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/css/timeline.css @@ -0,0 +1,106 @@ +/** + * Timeline + */ + +.timeline__legends { + font-size: 12px; + line-height: 1.5em; +} + +.timeline__legends span { + border-left-width: 10px; + border-left-style: solid; + padding: 0 10px 0 5px; +} + +.timeline__legends--default { + border-left-color: #e91e63; +} + +.timeline__legends--section { + border-left-color: #3f51b5; +} + +.timeline__legends--event_listener { + border-left-color: #00bcd4; +} + +.timeline__legends--event_listener_loading { + border-left-color: #8bc34a; +} + +.timeline__legends--template { + border-left-color: #ffc107; +} + +.timeline__legends--service { + border-left-color: #795548; +} + +text { + font-size: 12px; + line-height: 20px; + height: 22px; + fill: rgba(0, 0, 0, 0.87); +} +#timeline { + background: white; + margin: 10px 0; + width: 100%; + position: relative; + padding: 0 0 40px 0; +} +.timeline__row, +.timeline__scale--x, +.timeline__label rect { + stroke: rgba(0, 0, 0, 0.18); +} +.timeline__row { + fill: transparent; +} +.timeline__label rect { + fill: white; +} +.timeline__period--default { + fill: #e91e63; +} +.timeline__period--service { + fill: #795548; +} +.timeline__period--section { + fill: #3f51b5; +} +.timeline__period--event_listener { + fill: #00bcd4; +} +.timeline__period--event_listener_loading { + fill: #8bc34a; +} +.timeline__period--template { + fill: #ffc107; +} +.timeline__period-trigger { + fill: transparent; +} +.tooltip { + position: absolute; + padding: 8px; + background: rgb(0,0,0); + background: rgba(0, 0, 0, 0.87); + border-bottom-left-radius: 2px; + border-bottom-right-radius: 2px; + width: 175px; + text-align: center; + display: none; + color: white; +} +.tooltip__title { + display: block; +} +.tooltip__content { + display: block; +} +.axis { + stroke-width: 2px; + fill: none; +} diff --git a/web/modules/contrib/devel/webprofiler/images/caret-down.svg b/web/modules/contrib/devel/webprofiler/images/caret-down.svg new file mode 100644 index 000000000..4e48c8c98 --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/images/caret-down.svg @@ -0,0 +1 @@ + diff --git a/web/modules/contrib/devel/webprofiler/js/app/collections/collectors.js b/web/modules/contrib/devel/webprofiler/js/app/collections/collectors.js new file mode 100644 index 000000000..ed26836f3 --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/js/app/collections/collectors.js @@ -0,0 +1,35 @@ +(function ($, Drupal, drupalSettings, Backbone) { + + "use strict"; + + Drupal.webprofiler.collectors.Collectors = Backbone.Collection.extend({ + model: Drupal.webprofiler.models.Collector, + + url: Drupal.url('admin/reports/profiler/view/' + drupalSettings.webprofiler.token + '/collectors'), + + /** + * Unselect all models. + */ + resetSelected: function () { + this.each(function (model) { + model.set({"selected": false}); + }); + }, + + /** + * Select a specific model from the collection. + * + * @param id + * @returns {*} + */ + selectByID: function (id) { + this.resetSelected(); + var collector = this.get(id); + collector.set({"selected": true}); + + return collector.id; + } + }); + +}(jQuery, Drupal, drupalSettings, Backbone)); + diff --git a/web/modules/contrib/devel/webprofiler/js/app/helpers.js b/web/modules/contrib/devel/webprofiler/js/app/helpers.js new file mode 100644 index 000000000..caa8bdb4e --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/js/app/helpers.js @@ -0,0 +1,107 @@ +(function (drupalSettings) { + + Drupal.webprofiler.helpers = (function () { + + "use strict"; + + var escapeRx = function escapeRegExp(string) { + return string.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, "\\$1"); + }, + + repl = function replaceAll(string, find, replace) { + if (typeof string != 'string') { + return ''; + } + return string.replace(new RegExp(escapeRx(find), 'g'), replace); + }, + + shortLink = function (clazz) { + if (!clazz) { + return null; + } + clazz = repl(clazz, '/', '\\'); + var parts = clazz.split("\\"), result = [], size = (parts.length - 1); + + _.each(parts, function (item, key) { + if (key < size) { + result.push(item.substring(0, 1)); + } else { + result.push(item); + } + }); + return result.join("\\"); + }, + + abbr = function (clazz) { + if (!clazz) { + return null; + } + + return '' + shortLink(clazz) + ''; + }, + + ideLink = function (file, line) { + if (!file) { + return null; + } + + line = line || 0; + + file = file.replace(drupalSettings.webprofiler.ide_link_remote, drupalSettings.webprofiler.ide_link_local); + + return drupalSettings.webprofiler.ide_link.replace("@file", file).replace("@line", line); + }, + + classLink = function (data) { + var link = ideLink(data['file'], data['line']), clazz = abbr(data['class']), method = data['method'], output = ''; + + output = clazz; + if (method) { + output += '::' + method; + } + + if (link) { + output = '' + output + ''; + } + + return output; + }, + + printTime = function (data, unit) { + unit = unit || 'ms'; + data = Math.round((data + 0.00001) * 100) / 100; + return data + ' ' + unit; + }, + + frm = function (obj, level) { + level = level || 0; + var str = '
      ', prop; + if (typeof obj != 'object') { + return obj; + } + for (prop in obj) { + if (isInt(prop)) { + str += '
    • ' + frm(obj[prop], level + 1) + '
    • '; + } else { + str += '
    • ' + prop + ': ' + frm(obj[prop], level + 1) + '
    • '; + } + } + return str + '
    '; + }, + + isInt = function (value) { + var x; + return isNaN(value) ? !1 : (x = parseFloat(value), (0 | x) === x); + }; + + return { + frm: frm, + ideLink: ideLink, + shortLink: shortLink, + classLink: classLink, + printTime: printTime + } + + })(); + +}(drupalSettings)); diff --git a/web/modules/contrib/devel/webprofiler/js/app/main.js b/web/modules/contrib/devel/webprofiler/js/app/main.js new file mode 100644 index 000000000..d5d71cd42 --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/js/app/main.js @@ -0,0 +1,105 @@ +(function ($, Drupal, Backbone) { + + "use strict"; + + /** + * Define namespaces. + */ + Drupal.webprofiler = { + views: {}, + models: {}, + collectors: {}, + routers: {} + }; + + Drupal.behaviors.webprofiler = { + attach: function (context) { + var el, + elz, + key, + sel, + value, + select, + selector, + unselected, + filter = [], + + livefilter = function (e) { + el = $(e).attr('id').replace('edit-', ''); + value = $(e).val(); + filter[el] = value.replace('/', '\/'); + selector = []; + unselected = []; + + for (key in filter) { + if (filter[key].length > 0 && filter[key] != ' ') { + select = filter[key].split(' ').filter(Boolean); + for (sel in select) { + selector.push('[data-wp-' + key + ' *= ' + select[sel].toLowerCase() + ']'); + unselected.push('[data-wp-' + key + ']:not([data-wp-' + key + ' *= ' + select[sel].toLowerCase() + '])'); + } + } + else { + selector.push('[data-wp-' + key + ']'); + } + } + for (elz in unselected) { + $(unselected[elz]).addClass('is--hidden'); + } + $(selector.join('')).removeClass('is--hidden'); + }, + + modalFill = function(t,c){ + $('.modal__title').html(t); + $('.modal__main-data').html(c); + }, + + clipboard = function (e, t) { + var clip = e.parent().find(t).get(0), + title = 'Original Code', + content = ''; + + modalFill(title,content); + $('.modal').show(); + }; + + $(context).find('#collectors').once('webprofiler').each(function () { + new Drupal.webprofiler.routers.CollectorsRouter({el: $('#collectors')}); + Backbone.history.start({ + pushState: false + }); + }); + + $(context).find('.js--modal-close').each(function () { + $(this).on('click', function () { + $('.js--modal').hide(); + }); + }); + + $(context).find('.js--live-filter').each(function () { + $(this).on('keyup', function () { + livefilter($(this)); + }); + $(this).on('change', function () { + livefilter($(this)); + }); + }); + + $(context).find('.js--panel-toggle').once('js--panel-toggle').each(function () { + $(this).on('click', function () { + $(this).parent().parent().toggleClass('is--open'); + }); + }); + + $(context).find('.js--clipboard-trigger').once('js--clipboard-trigger').each(function () { + $(this).on('click', function () { + clipboard($(this), '.js--clipboard-target') + } + ); + }); + } + }; + +}(jQuery, Drupal, Backbone)); diff --git a/web/modules/contrib/devel/webprofiler/js/app/models/collector.js b/web/modules/contrib/devel/webprofiler/js/app/models/collector.js new file mode 100644 index 000000000..483033c47 --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/js/app/models/collector.js @@ -0,0 +1,15 @@ +(function ($, Drupal, drupalSettings, Backbone) { + + "use strict"; + + Drupal.webprofiler.models.Collector = Backbone.Model.extend({ + idAttribute: 'name', + urlRoot: Drupal.url('admin/reports/profiler/view/' + drupalSettings.webprofiler.token + '/collectors'), + defaults: { + name: "default", + data: [], + selected: false + } + }); + +}(jQuery, Drupal, drupalSettings, Backbone)); diff --git a/web/modules/contrib/devel/webprofiler/js/app/routers/collectors.js b/web/modules/contrib/devel/webprofiler/js/app/routers/collectors.js new file mode 100644 index 000000000..876f2387e --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/js/app/routers/collectors.js @@ -0,0 +1,48 @@ +(function ($, Drupal, drupalSettings, Backbone) { + + "use strict"; + + var collectors = new Drupal.webprofiler.collectors.Collectors(drupalSettings.webprofiler.collectors); + + Drupal.webprofiler.routers.CollectorsRouter = Backbone.Router.extend({ + routes: { + ':id': 'selectCollector' + }, + + /** + * + * @param id + */ + selectCollector: function (id) { + var collectors = this.collectors, layout = this.layout; + + //collectors.resetSelected(); + collectors.selectByID(id); + + var collector = collectors.get(id); + + if (collector.get('data').length != 0) { + layout.setDetails(collector); + } else { + var deferred = collectors.get(id).fetch(); + deferred.done(function () { + layout.setDetails(collector); + }); + } + }, + + /** + * + * @param options + */ + initialize: function (options) { + this.collectors = collectors; + this.layout = Drupal.webprofiler.views.Layout.getInstance({ + el: options.el, + router: this + }); + this.layout.render(); + } + }); + +}(jQuery, Drupal, drupalSettings, Backbone)); diff --git a/web/modules/contrib/devel/webprofiler/js/app/views/collector.js b/web/modules/contrib/devel/webprofiler/js/app/views/collector.js new file mode 100644 index 000000000..955a36e1b --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/js/app/views/collector.js @@ -0,0 +1,29 @@ +(function ($, Drupal, Backbone) { + + "use strict"; + + Drupal.webprofiler.views.CollectorView = Backbone.View.extend({ + tagName: 'li', + template: _.template($("script#collector").html()), + + /** + * + */ + initialize: function () { + _.bindAll(this, "render"); + this.listenTo(this.model, 'change:selected', this.render); + }, + + /** + * + * @returns {Drupal.webprofiler.views.CollectorView} + */ + render: function () { + this.$el.html(this.template(this.model.toJSON())); + this.$el.toggleClass('is--selected', this.model.get('selected')); + return this; + } + + }); + +}(jQuery, Drupal, Backbone)); diff --git a/web/modules/contrib/devel/webprofiler/js/app/views/collectorsList.js b/web/modules/contrib/devel/webprofiler/js/app/views/collectorsList.js new file mode 100644 index 000000000..8628371cb --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/js/app/views/collectorsList.js @@ -0,0 +1,21 @@ +(function ($, Drupal, Backbone) { + + "use strict"; + + Drupal.webprofiler.views.CollectorsList = Backbone.View.extend({ + tagName: 'section', + + /** + * + * @returns {Drupal.webprofiler.views.CollectorsList} + */ + render: function () { + var collectorsView = this.collection.map(function (collector) { + return (new Drupal.webprofiler.views.CollectorView({model: collector})).render().el; + }); + this.$el.html(collectorsView); + return this; + } + }); + +}(jQuery, Drupal, Backbone)); diff --git a/web/modules/contrib/devel/webprofiler/js/app/views/details.js b/web/modules/contrib/devel/webprofiler/js/app/views/details.js new file mode 100644 index 000000000..709f17ea7 --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/js/app/views/details.js @@ -0,0 +1,20 @@ +(function ($, Drupal, Backbone) { + + "use strict"; + + Drupal.webprofiler.views.DetailsView = Backbone.View.extend({ + el: '#details', + + /** + * + * @returns {Drupal.webprofiler.views.DetailsView} + */ + render: function () { + var template = _.template($("script#" + this.model.get('name')).html()); + + this.$el.html(template(this.model.toJSON())); + return this; + } + }); + +}(jQuery, Drupal, Backbone)); diff --git a/web/modules/contrib/devel/webprofiler/js/app/views/layout.js b/web/modules/contrib/devel/webprofiler/js/app/views/layout.js new file mode 100644 index 000000000..6168b3430 --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/js/app/views/layout.js @@ -0,0 +1,83 @@ +(function ($, Drupal, drupalSettings, Backbone) { + + "use strict"; + + Drupal.webprofiler.views.Layout = Backbone.View.extend({ + template: _.template( + '
    ' + + '
      ' + + '
      ' + + '
      ' + + 'Choose a collector.' + + '
      ' + ), + + /** + * + * @returns {Drupal.webprofiler.views.Layout} + */ + render: function () { + this.$el.html(this.template()); + + if(this.currentDetails) { + this.currentDetails.setElement(this.$('#details')).render(); + } + + this.overview.setElement(this.$('#overview ul')).render(); + return this; + }, + + /** + * + * @param options + */ + initialize: function (options) { + options.router.collectors.on('request', this.beginSync); + options.router.collectors.on('sync', this.finishSync); + + this.overview = new Drupal.webprofiler.views.CollectorsList({ + collection: options.router.collectors, + router: options.router + }); + }, + + /** + * + * @param collector + */ + setDetails: function (collector) { + if (this.currentDetails) this.currentDetails.remove(); + this.currentDetails = new Drupal.webprofiler.views.DetailsView({model: collector}); + this.render(); + Drupal.attachBehaviors(/*this.$el, drupalSettings*/); + }, + + /** + * + */ + beginSync: function () { + $('.loader--fixed').fadeIn({duration: 100}); + }, + + /** + * + */ + finishSync: function () { + $('.loader--fixed').fadeOut({duration: 100}); + } + }); + + var instance; + Drupal.webprofiler.views.Layout.getInstance = function (options) { + if (!instance) { + instance = new Drupal.webprofiler.views.Layout({ + el: options.el, + router: options.router, + collection: options.router.collectors + }); + } + + return instance; + } + +}(jQuery, Drupal, drupalSettings, Backbone)); diff --git a/web/modules/contrib/devel/webprofiler/js/database.js b/web/modules/contrib/devel/webprofiler/js/database.js new file mode 100644 index 000000000..5f5043079 --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/js/database.js @@ -0,0 +1,68 @@ +/** + * @file + * Database panel app. + */ +(function ($, Drupal, drupalSettings) { + + "use strict"; + + Drupal.behaviors.webprofiler_database = { + attach: function (context) { + $(context).find('.js--explain-trigger').once('js--explain-trigger').each(function () { + + $(this).on('click', function () { + var position = $(this).attr('data-wp-queryPosition'), + wrapper = $(this).parent().parent().find('.js--explain-target'), + loader = $(this).parent().parent().find('.js--loader'); + + if (wrapper.html().length === 0) { + + var url = Drupal.url('admin/reports/profiler/database_explain/' + drupalSettings.webprofiler.token + '/' + position); + + loader.show(); + + $.getJSON(url, function (data) { + _.templateSettings.variable = 'rc'; + var template = _.template( + $("#wp-query-explain-template").html() + ); + wrapper.html(template(data)); + loader.hide(); + delete _.templateSettings.variable; + }); + } + wrapper.toggle(); + + }); + }); + + $(context).find('.js--code-toggle').once('js--code-toggle').each(function () { + $(this).on('click', function () { + $(this).parent().find('.js--code-target').find('code').toggleClass('is--hidden'); + }); + }); + + $(context).find('.js--code-toggle--global').once('js--code-toggle--global').each(function () { + $(this).on('click', function () { + + if($(this).hasClass('js--placeholder-visible')){ + $('.js--placeholder-query').addClass('is--hidden'); + $('.js--original-query').removeClass('is--hidden'); + + }else{ + $('.js--placeholder-query').removeClass('is--hidden'); + $('.js--original-query').addClass('is--hidden'); + } + $(this).toggleClass('js--placeholder-visible'); + }); + }); + + if (typeof hljs != "undefined") { + $('code.sql').each(function (i, block) { + hljs.highlightBlock(block); + }); + } + } + } +}) +(jQuery, Drupal, drupalSettings); diff --git a/web/modules/contrib/devel/webprofiler/js/timeline.js b/web/modules/contrib/devel/webprofiler/js/timeline.js new file mode 100644 index 000000000..f550e080e --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/js/timeline.js @@ -0,0 +1,223 @@ +/** + * @file + * Timeline panel app. + */ +(function ($, Drupal, drupalSettings, d3) { + + 'use strict'; + + Drupal.behaviors.webprofiler_timeline = { + attach: function (context) { + if (typeof d3 != 'undefined') { + + // data + var data = drupalSettings.webprofiler.time.events; + var parts = []; + var dataL = data.length; + var perL; + var labelW = []; + var rowW; + var scalePadding; + var endTime = parseInt(data[(dataL - 1)].endtime); + var roundTime = Math.ceil(endTime / 1000) * 1000; + var endScale; + + for (var j = 0; j < dataL; j++) { + perL = data[j].periods.length; + for (var k = 0; k < perL; k++) { + parts.push({ + lane: j, + category: data[j].category, + memory: data[j].memory, + name: data[j].name, + start: data[j].periods[k].start, + end: data[j].periods[k].end + }); + } + } + + var tooltipCtrl = function (d, i) { + tooltip.html('memory usage: ' + d.memory + '' + + '' + parseInt(d.start) + 'ms ~ ' + parseInt(d.end) + 'ms'); + tooltip + .style('display', 'block') + .style('left', (d3.event.layerX - 87) + 'px') + .style('top', ((d.lane + 1) * 22) + 'px') + .style('opacity', .9); + }; + + var xscale = d3.scale.linear().domain([0, roundTime]).range([0, 1000]); + d3.select('#timeline').append('svg').attr('height', (dataL + 1) * 22 + 'px').attr('width', '100%').attr('class', 'timeline__canvas'); + + // tooltips + var tooltip = d3.select('#timeline') + .append('div') + .attr('class', 'tooltip'); + + + // Add a rectangle for every data element. + d3.select('.timeline__canvas') + .append('g') + .attr('class', 'timeline__rows') + .attr('x', 0) + .attr('y', 0) + .selectAll('g') + .data(data) + .enter() + .append('rect') + .attr('class', 'timeline__row') + .attr('x', 0) + .attr('y', function (d, i) { + return (i * 22); + }) + .attr('height', 22) + .attr('width', '100%') + .each(function () { + rowW = this.getBoundingClientRect().width; + }); + + // scale + var scale = d3.select('.timeline__canvas') + .append('g') + .attr('class', 'timeline__scale') + .attr('id', 'timeline__scale') + .attr('x', 0) + .attr('y', 0) + .selectAll('g') + .data(data) + .enter() + .append('a') + .attr('xlink:href', function (d) { + return Drupal.webprofiler.helpers.ideLink(d.link); + }) + .attr('class', function (d) { + return 'timeline__label ' + d.category; + }) + .attr('x', xscale(5)) + .attr('y', function (d, i) { + return (((i + 1) * 22) - 5); + }); + + scale.append('title') + .text(function (d) { + return d.name; + }); + + scale.append('text') + .attr('x', xscale(5)) + .attr('y', function (d, i) { + return (((i + 1) * 22) - 5); + }) + .text(function (d) { + return Drupal.webprofiler.helpers.shortLink(d.name); + }) + .each(function (d) { + labelW.push(this.getBoundingClientRect().width); + }); + + scalePadding = Math.max.apply(null, labelW) + 10; + + scale.insert('rect', 'title') + .attr('x', 0) + .attr('y', function (d, i) { + return (i * 22); + }) + .attr('height', 22) + .attr('stroke', 'transparent') + .attr('strokw-width', 1) + .attr('width', scalePadding); + + // times + var events = d3.select('.timeline__canvas') + .insert('g', '.timeline__scale') + .attr('class', 'timeline__parts') + .attr('x', 0) + .attr('y', 0) + .selectAll('g') + .data(parts) + .enter(); + + events.append('rect').attr('class', function (d) { + return 'timeline__period--' + d.category; + }) + .attr('x', function (d) { + return xscale(parseInt(d.start)) + scalePadding; + }) + .attr('y', function (d) { + return d.lane * 22; + }) + .attr('height', 22) + .attr('width', function (d) { + return xscale(Math.max(parseInt(d.end - d.start), 1)); + }); + + events.append('rect') + .attr('class', function (d) { + return 'timeline__period-trigger'; + }) + .attr('x', function (d) { + return xscale(parseInt(d.start)) + scalePadding - 5; + }) + .attr('y', function (d) { + return d.lane * 22; + }) + .attr('height', 22) + .attr('width', function (d) { + return xscale(Math.max(parseInt(d.end - d.start), 1)) + 11; + }) + .on('mouseover', function (d, i) { + tooltipCtrl(d, i); + }) + .on('mouseout', function (d) { + tooltip + .style('display', 'none'); + }); + + // Draw X-axis grid lines + d3.select('.timeline__parts').insert('g', '.timeline__parts') + .selectAll('line') + .data(xscale.ticks(10)) + .enter() + .append('line') + .attr('class', 'timeline__scale--x') + .attr('x1', xscale) + .attr('x2', xscale) + .attr('y1', 0) + .attr('y2', data.length * 22) + .attr('transform', 'translate( ' + scalePadding + ' , 0)'); + + var xAxis = d3.svg.axis().scale(xscale).ticks(10).orient('bottom').tickFormat(function (d) { + return d + ' ms'; + }); + + d3.select('.timeline__parts').insert('g', '.timeline__parts') + .attr('class', 'axis') + .attr('transform', 'translate(' + scalePadding + ', ' + dataL * 22 + ')') + .call(xAxis); + + endScale = xscale(endTime) - rowW - parseInt(scalePadding); + + if (parseInt(xscale(endTime)) > (rowW - parseInt(scalePadding))) { + d3.select('.timeline__canvas') + .call( + d3.behavior.zoom() + .scaleExtent([1, 1]) + .x(xscale) + .on('zoom', function () { + + var t = d3.event.translate; + var tx = t[0]; + + tx = tx > 0 ? 0 : tx; + + tx = tx < endScale ? endScale : tx; + + d3.select('.timeline__parts').attr('transform', 'translate( ' + tx + ' , 0)'); + })); + } + + } + } + }; + +})(jQuery, Drupal, drupalSettings, d3); diff --git a/web/modules/contrib/devel/webprofiler/src/Access/AccessManagerWrapper.php b/web/modules/contrib/devel/webprofiler/src/Access/AccessManagerWrapper.php new file mode 100644 index 000000000..7911e41f5 --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/src/Access/AccessManagerWrapper.php @@ -0,0 +1,84 @@ +currentUser; + } + $route = $route_match->getRouteObject(); + $checks = $route->getOption('_access_checks') ?: []; + + // Filter out checks which require the incoming request. + if (!isset($request)) { + $checks = array_diff($checks, $this->checkProvider->getChecksNeedRequest()); + } + + $result = AccessResult::neutral(); + if (!empty($checks)) { + $arguments_resolver = $this->argumentsResolverFactory->getArgumentsResolver($route_match, $account, $request); + + if (!$checks) { + return AccessResult::neutral(); + } + $result = AccessResult::allowed(); + foreach ($checks as $service_id) { + $result = $result->andIf($this->performCheck($service_id, $arguments_resolver, $request)); + } + } + return $return_as_object ? $result : $result->isAllowed(); + } + + /** + * {@inheritdoc} + */ + protected function performCheck($service_id, ArgumentsResolverInterface $arguments_resolver, Request $request = NULL) { + $callable = $this->checkProvider->loadCheck($service_id); + $arguments = $arguments_resolver->getArguments($callable); + /** @var \Drupal\Core\Access\AccessResultInterface $service_access **/ + $service_access = call_user_func_array($callable, $arguments); + + if (!$service_access instanceof AccessResultInterface) { + throw new AccessException("Access error in $service_id. Access services must return an object that implements AccessResultInterface."); + } + + if ($request) { + $this->dataCollector->addAccessCheck($service_id, $callable, $request); + } + + return $service_access; + } + + /** + * @param \Drupal\webprofiler\DataCollector\RequestDataCollector $dataCollector + */ + public function setDataCollector(RequestDataCollector $dataCollector) { + $this->dataCollector = $dataCollector; + } + +} diff --git a/web/modules/contrib/devel/webprofiler/src/Asset/CssCollectionRendererWrapper.php b/web/modules/contrib/devel/webprofiler/src/Asset/CssCollectionRendererWrapper.php new file mode 100644 index 000000000..66b74920b --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/src/Asset/CssCollectionRendererWrapper.php @@ -0,0 +1,41 @@ +assetCollectionRenderer = $assetCollectionRenderer; + $this->dataCollector = $dataCollector; + } + + /** + * {@inheritdoc} + */ + public function render(array $css_assets) { + $this->dataCollector->addCssAsset($css_assets); + + return $this->assetCollectionRenderer->render($css_assets); + } + +} diff --git a/web/modules/contrib/devel/webprofiler/src/Asset/JsCollectionRendererWrapper.php b/web/modules/contrib/devel/webprofiler/src/Asset/JsCollectionRendererWrapper.php new file mode 100644 index 000000000..98cd3fefd --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/src/Asset/JsCollectionRendererWrapper.php @@ -0,0 +1,41 @@ +assetCollectionRenderer = $assetCollectionRenderer; + $this->dataCollector = $dataCollector; + } + + /** + * {@inheritdoc} + */ + public function render(array $js_assets) { + $this->dataCollector->addJsAsset($js_assets); + + return $this->assetCollectionRenderer->render($js_assets); + } + +} diff --git a/web/modules/contrib/devel/webprofiler/src/Cache/CacheBackendWrapper.php b/web/modules/contrib/devel/webprofiler/src/Cache/CacheBackendWrapper.php new file mode 100644 index 000000000..4050b4103 --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/src/Cache/CacheBackendWrapper.php @@ -0,0 +1,176 @@ +cacheDataCollector = $cacheDataCollector; + $this->cacheBackend = $cacheBackend; + $this->bin = $bin; + } + + /** + * {@inheritdoc} + */ + public function get($cid, $allow_invalid = FALSE) { + $cache = $this->cacheBackend->get($cid, $allow_invalid); + + if ($cache) { + $cacheCopy = new \stdClass(); + $cacheCopy->cid = $cache->cid; + $cacheCopy->expire = $cache->expire; + $cacheCopy->tags = $cache->tags; + + $this->cacheDataCollector->registerCacheHit($this->bin, $cacheCopy); + } + else { + $this->cacheDataCollector->registerCacheMiss($this->bin, $cid); + } + + return $cache; + } + + /** + * {@inheritdoc} + */ + public function getMultiple(&$cids, $allow_invalid = FALSE) { + $cidsCopy = $cids; + $cache = $this->cacheBackend->getMultiple($cids, $allow_invalid); + + foreach ($cidsCopy as $cid) { + if (in_array($cid, $cids)) { + $this->cacheDataCollector->registerCacheMiss($this->bin, $cid); + } + else { + $cacheCopy = new \stdClass(); + $cacheCopy->cid = $cache[$cid]->cid; + $cacheCopy->expire = $cache[$cid]->expire; + $cacheCopy->tags = $cache[$cid]->tags; + + $this->cacheDataCollector->registerCacheHit($this->bin, $cacheCopy); + } + } + + return $cache; + } + + /** + * {@inheritdoc} + */ + public function set($cid, $data, $expire = Cache::PERMANENT, array $tags = []) { + return $this->cacheBackend->set($cid, $data, $expire, $tags); + } + + /** + * {@inheritdoc} + */ + public function setMultiple(array $items) { + return $this->cacheBackend->setMultiple($items); + } + + /** + * {@inheritdoc} + */ + public function delete($cid) { + return $this->cacheBackend->delete($cid); + } + + /** + * {@inheritdoc} + */ + public function deleteMultiple(array $cids) { + return $this->cacheBackend->deleteMultiple($cids); + } + + /** + * {@inheritdoc} + */ + public function deleteAll() { + return $this->cacheBackend->deleteAll(); + } + + /** + * {@inheritdoc} + */ + public function invalidate($cid) { + return $this->cacheBackend->invalidate($cid); + } + + /** + * {@inheritdoc} + */ + public function invalidateMultiple(array $cids) { + return $this->cacheBackend->invalidateMultiple($cids); + } + + /** + * {@inheritdoc} + */ + public function invalidateTags(array $tags) { + if ($this->cacheBackend instanceof CacheTagsInvalidatorInterface) { + $this->cacheBackend->invalidateTags($tags); + } + } + + /** + * {@inheritdoc} + */ + public function invalidateAll() { + return $this->cacheBackend->invalidateAll(); + } + + /** + * {@inheritdoc} + */ + public function garbageCollection() { + return $this->cacheBackend->garbageCollection(); + } + + /** + * {@inheritdoc} + */ + public function removeBin() { + return $this->cacheBackend->removeBin(); + } + +} diff --git a/web/modules/contrib/devel/webprofiler/src/Cache/CacheFactoryWrapper.php b/web/modules/contrib/devel/webprofiler/src/Cache/CacheFactoryWrapper.php new file mode 100644 index 000000000..d37e8941b --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/src/Cache/CacheFactoryWrapper.php @@ -0,0 +1,62 @@ +cacheFactory = $cache_factory; + $this->cacheDataCollector = $cacheDataCollector; + } + + /** + * {@inheritdoc} + */ + public function get($bin) { + if (!isset($this->cacheBackends[$bin])) { + $cache_backend = $this->cacheFactory->get($bin); + $this->cacheBackends[$bin] = new CacheBackendWrapper($this->cacheDataCollector, $cache_backend, $bin); + } + return $this->cacheBackends[$bin]; + } + +} diff --git a/web/modules/contrib/devel/webprofiler/src/Command/BenchmarkCommand.php b/web/modules/contrib/devel/webprofiler/src/Command/BenchmarkCommand.php new file mode 100644 index 000000000..ab6dc25e9 --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/src/Command/BenchmarkCommand.php @@ -0,0 +1,312 @@ +setName('webprofiler:benchmark') + ->setDescription($this->trans('commands.webprofiler.benchmark.description')) + ->addArgument('url', InputArgument::REQUIRED, $this->trans('commands.webprofiler.benchmark.arguments.url')) + ->addOption('runs', NULL, InputOption::VALUE_REQUIRED, $this->trans('commands.webprofiler.benchmark.options.runs'), 100) + ->addOption('file', NULL, InputOption::VALUE_REQUIRED, $this->trans('commands.webprofiler.benchmark.options.file')) + ->addOption('cache-rebuild', 'cr', InputOption::VALUE_NONE, $this->trans('commands.webprofiler.benchmark.options.cache_rebuild')); + } + + /** + * {@inheritdoc} + */ + protected function execute(InputInterface $input, OutputInterface $output) { + $runs = $input->getOption('runs'); + $file = $input->getOption('file'); + $cache_rebuild = $input->getOption('cache-rebuild'); + + // http://username:password@hostname/ + $url = $input->getArgument('url'); + $url_components = parse_url($url); + $login = isset($url_components['user']) && isset($url_components['pass']); + + $steps = 3; + + if ($cache_rebuild) { + $steps++; + } + + if ($login) { + $steps++; + } + + /** @var \Drupal\Core\Http\Client $client */ + $client = $this->container->get('http_client'); + + $progress = new ProgressBar($output, $runs + $steps); + $progress->setFormat(' %current%/%max% [%bar%] %percent:3s%% %message%'); + + if ($cache_rebuild) { + $progress->setMessage($this->trans('commands.webprofiler.benchmark.progress.cache_rebuild')); + $this->RebuildCache(); + $progress->advance(); + } + + if ($login) { + $progress->setMessage($this->trans('commands.webprofiler.benchmark.progress.login')); + $login_url = "{$url_components['scheme']}://{$url_components['host']}/user/login"; + + // Enable cookies storage. + $cookieJar = new CookieJar(); + $client->setDefaultOption('cookies', $cookieJar); + + // Retrieve a form_build_id using the DomCrawler component. + $response = $client->get($login_url)->getBody()->getContents(); + $crawler = new Crawler($response); + $form_build_id = $crawler->filter('#user-login-form input[name=form_build_id]') + ->attr('value'); + $op = $crawler->filter('#user-login-form input[name=op]')->attr('value'); + + // Login a user. + $response = $client->post($login_url, [ + 'body' => [ + 'name' => $url_components['user'], + 'pass' => $url_components['pass'], + 'form_build_id' => $form_build_id, + 'form_id' => 'user_login_form', + 'op' => $op, + ], + ]); + $progress->advance(); + + if ($response->getStatusCode() != 200) { + throw new \Exception($this->trans('commands.webprofiler.benchmark.messages.error_login')); + } + } + + $datas = []; + for ($i = 0; $i < $runs; $i++) { + $progress->setMessage($this->trans('commands.webprofiler.benchmark.progress.get')); + $datas[] = $this->getData($client, $url); + $progress->advance(); + } + + $progress->setMessage($this->trans('commands.webprofiler.benchmark.progress.compute_avg')); + $avg = $this->computeAvg($datas); + $progress->advance(); + + $progress->setMessage($this->trans('commands.webprofiler.benchmark.progress.compute_median')); + $median = $this->computePercentile($datas, 50); + $progress->advance(); + + $progress->setMessage($this->trans('commands.webprofiler.benchmark.progress.compute_95percentile')); + $percentile95 = $this->computePercentile($datas, 95); + $progress->advance(); + + $progress->setMessage($this->trans('commands.webprofiler.benchmark.progress.git_hash')); + $gitHash = $this->getGitHash(); + $progress->advance(); + + $progress->setMessage($this->trans('commands.webprofiler.benchmark.progress.yaml')); + $yaml = $this->generateYaml($gitHash, $runs, $url, $avg, $median, $percentile95); + $progress->advance(); + + $progress->setMessage($this->trans('commands.webprofiler.benchmark.progress.done')); + $progress->finish(); + $output->writeln(''); + + if ($file) { + file_put_contents($file, $yaml); + } + else { + $output->writeln($yaml); + } + + } + + /** + * @param \GuzzleHttp\ClientInterface $client + * @param $url + * + * @return array + */ + private function getData(ClientInterface $client, $url) { + /** @var \GuzzleHttp\Message\ResponseInterface $response */ + $response = $client->get($url); + + $token = $response->getHeader('X-Debug-Token'); + + /** @var \Drupal\webprofiler\Profiler\Profiler $profiler */ + $profiler = $this->container->get('profiler'); + + /** @var \Symfony\Component\HttpKernel\Profiler\Profile $profile */ + $profile = $profiler->loadProfile($token); + + /** @var \Drupal\webprofiler\DataCollector\TimeDataCollector $timeDataCollector */ + $timeDataCollector = $profile->getCollector('time'); + + return new BenchmarkData( + $token, + $timeDataCollector->getMemory(), + $timeDataCollector->getDuration()); + } + + /** + * @param \Drupal\webprofiler\Command\BenchmarkData[] $datas + * + * @return \Drupal\webprofiler\Command\BenchmarkData + */ + private function computeAvg($datas) { + $profiles = count($datas); + $totalTime = 0; + $totalMemory = 0; + + foreach ($datas as $data) { + $totalTime += $data->getTime(); + $totalMemory += $data->getMemory(); + } + + return new BenchmarkData(NULL, $totalMemory / $profiles, $totalTime / $profiles); + } + + /** + * Computes percentile using The Nearest Rank method. + * + * @param \Drupal\webprofiler\Command\BenchmarkData[] $datas + * @param $percentile + * + * @return \Drupal\webprofiler\Command\BenchmarkData + * + * @throws \Exception + */ + private function computePercentile($datas, $percentile) { + if ($percentile < 0 || $percentile > 100) { + throw new \Exception('Percentile has to be between 0 and 100'); + } + + $profiles = count($datas); + + $n = ceil((($percentile / 100) * $profiles)); + $index = (int) $n - 1; + + $orderedTime = $datas; + $this->getOrderedDatas($orderedTime, 'Time'); + + $orderedMemory = $datas; + $this->getOrderedDatas($orderedMemory, 'Memory'); + + return new BenchmarkData(NULL, $orderedMemory[$index]->getMemory(), $orderedTime[$index]->getTime()); + } + + /** + * @return string + */ + private function getGitHash() { + try { + $process = new Process('git rev-parse HEAD'); + $process->setTimeout(3600); + $process->run(); + $git_hash = $process->getOutput(); + } + catch (\Exception $e) { + $git_hash = $this->trans('commands.webprofiler.benchmark.messages.not_git'); + } + + return $git_hash; + } + + /** + * @param \Drupal\webprofiler\Command\BenchmarkData[] $datas + * @param $string + * + * @return array + */ + private function getOrderedDatas(&$datas, $string) { + usort($datas, function ($a, $b) use ($string) { + $method = 'get' . $string; + if ($a->{$method} > $b->{$method}) { + return 1; + } + if ($a->{$method} < $b->{$method}) { + return -1; + } + return 0; + }); + } + + /** + * Rebuilds Drupal cache. + */ + protected function RebuildCache() { + require_once DRUPAL_ROOT . '/core/includes/utility.inc'; + $kernelHelper = $this->getHelper('kernel'); + $classLoader = $kernelHelper->getClassLoader(); + $request = $kernelHelper->getRequest(); + drupal_rebuild($classLoader, $request); + } + + /** + * @param $gitHash + * @param $runs + * @param $url + * @param \Drupal\webprofiler\Command\BenchmarkData $avg + * @param \Drupal\webprofiler\Command\BenchmarkData $median + * @param \Drupal\webprofiler\Command\BenchmarkData $percentile95 + * + * @return string + */ + private function generateYaml($gitHash, $runs, $url, BenchmarkData $avg, BenchmarkData $median, BenchmarkData $percentile95) { + $yaml = Yaml::dump([ + 'date' => date($this->trans('commands.webprofiler.list.rows.time'), time()), + 'git_commit' => $gitHash, + 'number_of_runs' => $runs, + 'url' => $url, + 'results' => [ + 'average' => [ + 'time' => sprintf('%.0f ms', $avg->getTime()), + 'memory' => sprintf('%.1f MB', $avg->getMemory() / 1024 / 1024), + ], + 'median' => [ + 'time' => sprintf('%.0f ms', $median->getTime()), + 'memory' => sprintf('%.1f MB', $median->getMemory() / 1024 / 1024), + ], + '95_percentile' => [ + 'time' => sprintf('%.0f ms', $percentile95->getTime()), + 'memory' => sprintf('%.1f MB', $percentile95->getMemory() / 1024 / 1024), + ], + ], + ]); + return $yaml; + } + + /** + * {@inheritdoc} + */ + public function showMessage($output, $message, $type = 'info') { + } + +} diff --git a/web/modules/contrib/devel/webprofiler/src/Command/BenchmarkData.php b/web/modules/contrib/devel/webprofiler/src/Command/BenchmarkData.php new file mode 100644 index 000000000..38f9f08b8 --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/src/Command/BenchmarkData.php @@ -0,0 +1,57 @@ +token = $token; + $this->memory = $memory; + $this->time = $time; + } + + /** + * @return mixed + */ + public function getToken() { + return $this->token; + } + + /** + * @return mixed + */ + public function getMemory() { + return $this->memory; + } + + /** + * @return mixed + */ + public function getTime() { + return $this->time; + } + +} diff --git a/web/modules/contrib/devel/webprofiler/src/Command/ExportCommand.php b/web/modules/contrib/devel/webprofiler/src/Command/ExportCommand.php new file mode 100644 index 000000000..c21def2eb --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/src/Command/ExportCommand.php @@ -0,0 +1,164 @@ +setName('webprofiler:export') + ->setDescription($this->trans('commands.webprofiler.export.description')) + ->addArgument('id', InputArgument::OPTIONAL, $this->trans('commands.webprofiler.export.arguments.id')) + ->addOption('directory', NULL, InputOption::VALUE_REQUIRED, $this->trans('commands.webprofiler.export.options.directory'), '/tmp'); + } + + /** + * {@inheritdoc} + */ + protected function execute(InputInterface $input, OutputInterface $output) { + $id = $input->getArgument('id'); + $directory = $input->getOption('directory'); + + /** @var \Drupal\webprofiler\Profiler\Profiler $profiler */ + $profiler = $this->container->get('profiler'); + + try { + if ($id) { + $this->filename = $this->exportSingle($profiler, $id, $directory); + } + else { + $this->filename = $this->exportAll($profiler, $directory, $output); + } + + } + catch (\Exception $e) { + $output->writeln('' . $e->getMessage() . ''); + } + } + + /** + * Exports a single profile. + * + * @param \Drupal\webprofiler\Profiler\Profiler $profiler + * @param int $id + * @param string $directory + * + * @return string + * + * @throws \Exception + */ + private function exportSingle(Profiler $profiler, $id, $directory) { + $profile = $profiler->loadProfile($id); + if ($profile) { + $data = $profiler->export($profile); + + $filename = $directory . DIRECTORY_SEPARATOR . $id . '.txt'; + if (file_put_contents($filename, $data) === FALSE) { + throw new \Exception(sprintf( + $this->trans('commands.webprofiler.export.messages.error_writing'), + $filename)); + } + } + else { + throw new \Exception(sprintf( + $this->trans('commands.webprofiler.export.messages.error_no_profile'), + $id)); + } + + return $filename; + } + + /** + * Exports all stored profiles (cap limit at 1000 items). + * + * @param \Drupal\webprofiler\Profiler\Profiler $profiler + * @param string $directory + * @param \Symfony\Component\Console\Output\OutputInterface $output + * + * @return string + */ + private function exportAll(Profiler $profiler, $directory, $output) { + $filename = $directory . DIRECTORY_SEPARATOR . 'profiles_' . time() . '.tar.gz'; + $archiver = new ArchiveTar($filename, 'gz'); + $profiles = $profiler->find(NULL, NULL, 1000, NULL, '', ''); + $progress = new ProgressBar($output, count($profiles) + 2); + $progress->setFormat(' %current%/%max% [%bar%] %percent:3s%% %message%'); + + $files = []; + $progress->start(); + $progress->setMessage($this->trans('commands.webprofiler.export.progress.exporting')); + foreach ($profiles as $profile) { + $data = $profiler->export($profiler->loadProfile($profile['token'])); + $profileFilename = $directory . "/{$profile['token']}.txt"; + file_put_contents($profileFilename, $data); + $files[] = $profileFilename; + $progress->advance(); + } + + $progress->setMessage($this->trans('commands.webprofiler.export.progress.archive')); + $archiver->createModify($files, '', $directory); + $progress->advance(); + + $progress->setMessage($this->trans('commands.webprofiler.export.progress.delete_tmp')); + foreach ($files as $file) { + unlink($file); + } + $progress->advance(); + + $progress->setMessage($this->trans('commands.webprofiler.export.progress.done')); + $progress->finish(); + $output->writeln(''); + + $output->writeln(sprintf( + $this->trans('commands.webprofiler.export.messages.exported_count'), + count($profiles))); + + return $filename; + } + + /** + * {@inheritdoc} + */ + public function showMessage($output, $message, $type = 'info') { + if (!$this->filename) { + return; + } + + $completeMessageKey = 'commands.webprofiler.export.messages.success'; + $completeMessage = sprintf($this->trans($completeMessageKey), $this->filename); + + if ($completeMessage != $completeMessageKey) { + parent::showMessage($output, $completeMessage); + } + } + +} diff --git a/web/modules/contrib/devel/webprofiler/src/Command/ListCommand.php b/web/modules/contrib/devel/webprofiler/src/Command/ListCommand.php new file mode 100644 index 000000000..7957ace79 --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/src/Command/ListCommand.php @@ -0,0 +1,83 @@ +setName('webprofiler:list') + ->setDescription($this->trans('commands.webprofiler.list.description')) + ->addOption('ip', NULL, InputOption::VALUE_REQUIRED, $this->trans('commands.webprofiler.list.options.ip'), NULL) + ->addOption('url', NULL, InputOption::VALUE_REQUIRED, $this->trans('commands.webprofiler.list.options.url'), NULL) + ->addOption('method', NULL, InputOption::VALUE_REQUIRED, $this->trans('commands.webprofiler.list.options.method'), NULL) + ->addOption('limit', NULL, InputOption::VALUE_REQUIRED, $this->trans('commands.webprofiler.list.options.limit'), 10); + } + + /** + * {@inheritdoc} + */ + protected function execute(InputInterface $input, OutputInterface $output) { + $ip = $input->getOption('ip'); + $url = $input->getOption('url'); + $method = $input->getOption('method'); + $limit = $input->getOption('limit'); + + /** @var \Drupal\webprofiler\Profiler\Profiler $profiler */ + $profiler = $this->container->get('profiler'); + $profiles = $profiler->find($ip, $url, $limit, $method, '', ''); + + $rows = []; + foreach ($profiles as $profile) { + $row = []; + + $row[] = $profile['token']; + $row[] = $profile['ip']; + $row[] = $profile['method']; + $row[] = $profile['url']; + $row[] = date($this->trans('commands.webprofiler.list.rows.time'), $profile['time']); + + $rows[] = $row; + } + + $table = new Table($output); + $table + ->setHeaders([ + $this->trans('commands.webprofiler.list.header.token'), + $this->trans('commands.webprofiler.list.header.ip'), + $this->trans('commands.webprofiler.list.header.method'), + $this->trans('commands.webprofiler.list.header.url'), + $this->trans('commands.webprofiler.list.header.time'), + ]) + ->setRows($rows); + $table->render(); + } + + /** + * {@inheritdoc} + */ + public function showMessage($output, $message, $type = 'info') { + } + +} diff --git a/web/modules/contrib/devel/webprofiler/src/Compiler/DecoratorPass.php b/web/modules/contrib/devel/webprofiler/src/Compiler/DecoratorPass.php new file mode 100644 index 000000000..be4f8f1ed --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/src/Compiler/DecoratorPass.php @@ -0,0 +1,34 @@ +findDefinition('plugin.manager.mail'); + $definition->setPublic(FALSE); + $container->setDefinition('webprofiler.debug.plugin.manager.mail.default', $definition); + $container->register('plugin.manager.mail', 'Drupal\webprofiler\Mail\MailManagerWrapper') + ->addArgument(new Reference('container.namespaces')) + ->addArgument(new Reference('cache.discovery')) + ->addArgument(new Reference('module_handler')) + ->addArgument(new Reference('config.factory')) + ->addArgument(new Reference('logger.factory')) + ->addArgument(new Reference('string_translation')) + ->addArgument(new Reference('webprofiler.debug.plugin.manager.mail.default')) + ->addArgument(new Reference('webprofiler.mail')) + ->setProperty('_serviceId', 'plugin.manager.mail'); + } + +} diff --git a/web/modules/contrib/devel/webprofiler/src/Compiler/ProfilerPass.php b/web/modules/contrib/devel/webprofiler/src/Compiler/ProfilerPass.php new file mode 100644 index 000000000..8aeff4367 --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/src/Compiler/ProfilerPass.php @@ -0,0 +1,65 @@ +hasDefinition('profiler')) { + return; + } + + $definition = $container->getDefinition('profiler'); + + $collectors = new \SplPriorityQueue(); + $order = PHP_INT_MAX; + foreach ($container->findTaggedServiceIds('data_collector') as $id => $attributes) { + $priority = isset($attributes[0]['priority']) ? $attributes[0]['priority'] : 0; + $template = NULL; + + if (isset($attributes[0]['template'])) { + if (!isset($attributes[0]['id'])) { + throw new \InvalidArgumentException(sprintf('Data collector service "%s" must have an id attribute in order to specify a template', $id)); + } + if (!isset($attributes[0]['title'])) { + throw new \InvalidArgumentException(sprintf('Data collector service "%s" must have a title attribute', $id)); + } + + $template = [ + $attributes[0]['id'], + $attributes[0]['template'], + $attributes[0]['title'], + ]; + } + + $collectors->insert([$id, $template], [-$priority, --$order]); + } + + $templates = []; + foreach ($collectors as $collector) { + $definition->addMethodCall('add', [new Reference($collector[0])]); + $templates[$collector[0]] = $collector[1]; + } + + $container->setParameter('data_collector.templates', $templates); + + // Set parameter to store the public folder path. + $path = 'file:' . DRUPAL_ROOT . '/' . PublicStream::basePath() . '/profiler'; + $container->setParameter('data_collector.storage', $path); + } + +} diff --git a/web/modules/contrib/devel/webprofiler/src/Compiler/ServicePass.php b/web/modules/contrib/devel/webprofiler/src/Compiler/ServicePass.php new file mode 100644 index 000000000..00c2617a0 --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/src/Compiler/ServicePass.php @@ -0,0 +1,109 @@ +hasDefinition('webprofiler.services')) { + return; + } + + $definition = $container->getDefinition('webprofiler.services'); + $graph = $container->getCompiler()->getServiceReferenceGraph(); + + $definition->addMethodCall('setServices', [$this->extractData($container, $graph)]); + } + + /** + * @param \Symfony\Component\DependencyInjection\ContainerBuilder $container + * @param \Symfony\Component\DependencyInjection\Compiler\ServiceReferenceGraph $graph + * + * @return array + */ + private function extractData(ContainerBuilder $container, ServiceReferenceGraph $graph) { + $data = []; + + foreach ($container->getDefinitions() as $id => $definition) { + $inEdges = []; + $outEdges = []; + + if ($graph->hasNode($id)) { + $node = $graph->getNode($id); + + /** @var \Symfony\Component\DependencyInjection\Compiler\ServiceReferenceGraphEdge $edge */ + foreach ($node->getInEdges() as $edge) { + /** @var \Symfony\Component\DependencyInjection\Reference $edgeValue */ + $edgeValue = $edge->getValue(); + + $inEdges[] = [ + 'id' => $edge->getSourceNode()->getId(), + 'invalidBehavior' => $edgeValue ? $edgeValue->getInvalidBehavior() : NULL, + ]; + } + + /** @var \Symfony\Component\DependencyInjection\Compiler\ServiceReferenceGraphEdge $edge */ + foreach ($node->getOutEdges() as $edge) { + /** @var \Symfony\Component\DependencyInjection\Reference $edgeValue */ + $edgeValue = $edge->getValue(); + + $outEdges[] = [ + 'id' => $edge->getDestNode()->getId(), + 'invalidBehavior' => $edgeValue ? $edgeValue->getInvalidBehavior() : NULL, + ]; + } + } + + if ($definition instanceof Definition) { + $class = $definition->getClass(); + + try { + $reflectedClass = new \ReflectionClass($class); + $file = $reflectedClass->getFileName(); + } + catch (\ReflectionException $e) { + $file = NULL; + } + + $tags = $definition->getTags(); + $public = $definition->isPublic(); + $synthetic = $definition->isSynthetic(); + } + else { + $id = $definition->__toString(); + $class = NULL; + $file = NULL; + $tags = []; + $public = NULL; + $synthetic = NULL; + } + + $data[$id] = [ + 'inEdges' => $inEdges, + 'outEdges' => $outEdges, + 'value' => [ + 'class' => $class, + 'file' => $file, + 'id' => $id, + 'tags' => $tags, + 'public' => $public, + 'synthetic' => $synthetic, + ], + ]; + } + + return $data; + } + +} diff --git a/web/modules/contrib/devel/webprofiler/src/Compiler/StoragePass.php b/web/modules/contrib/devel/webprofiler/src/Compiler/StoragePass.php new file mode 100644 index 000000000..2acbe758c --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/src/Compiler/StoragePass.php @@ -0,0 +1,33 @@ +hasDefinition('profiler.storage_manager')) { + return; + } + + $definition = $container->getDefinition('profiler.storage_manager'); + + foreach ($container->findTaggedServiceIds('webprofiler_storage') as $id => $attributes) { + $definition->addMethodCall('addStorage', [ + $id, + $attributes[0]['title'], + new Reference($id), + ]); + } + } + +} diff --git a/web/modules/contrib/devel/webprofiler/src/Config/ConfigFactoryWrapper.php b/web/modules/contrib/devel/webprofiler/src/Config/ConfigFactoryWrapper.php new file mode 100644 index 000000000..a9259b3c9 --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/src/Config/ConfigFactoryWrapper.php @@ -0,0 +1,45 @@ +dataCollector->addConfigName($name); + return $result; + } + + /** + * {@inheritdoc} + */ + public function loadMultiple(array $names) { + $result = parent::loadMultiple($names); + foreach (array_keys($result) as $name) { + $this->dataCollector->addConfigName($name); + } + return $result; + } + + /** + * @param \Drupal\webprofiler\DataCollector\ConfigDataCollector $dataCollector + */ + public function setDataCollector(ConfigDataCollector $dataCollector) { + $this->dataCollector = $dataCollector; + } + +} diff --git a/web/modules/contrib/devel/webprofiler/src/Controller/DashboardController.php b/web/modules/contrib/devel/webprofiler/src/Controller/DashboardController.php new file mode 100644 index 000000000..d1bb0bedc --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/src/Controller/DashboardController.php @@ -0,0 +1,244 @@ +get('profiler'), + $container->get('router'), + $container->get('template_manager'), + $container->get('date.formatter'), + $container->get('profiler.storage_manager') + ); + } + + /** + * Constructs a new WebprofilerController. + * + * @param \Drupal\webprofiler\Profiler\Profiler $profiler + * @param \Symfony\Component\Routing\RouterInterface $router + * @param \Drupal\webprofiler\Profiler\TemplateManager $templateManager + * @param \Drupal\Core\Datetime\DateFormatter $date + * @param \Drupal\webprofiler\Profiler\ProfilerStorageManager $storageManager + */ + public function __construct(Profiler $profiler, RouterInterface $router, TemplateManager $templateManager, DateFormatter $date, ProfilerStorageManager $storageManager) { + $this->profiler = $profiler; + $this->router = $router; + $this->templateManager = $templateManager; + $this->date = $date; + $this->storageManager = $storageManager; + } + + /** + * Generates the dashboard page. + * + * @param \Symfony\Component\HttpKernel\Profiler\Profile $profile + * + * @return array + */ + public function dashboardAction(Profile $profile) { + $this->profiler->disable(); + + $templateManager = $this->templateManager; + $templates = $templateManager->getTemplates($profile); + + $panels = []; + $libraries = ['webprofiler/dashboard']; + $drupalSettings = [ + 'webprofiler' => [ + 'token' => $profile->getToken(), + 'ide_link' => $this->config('webprofiler.config')->get('ide_link'), + 'ide_link_remote' => $this->config('webprofiler.config')->get('ide_link_remote'), + 'ide_link_local' => $this->config('webprofiler.config')->get('ide_link_local'), + 'collectors' => [], + ], + ]; + + foreach ($templates as $name => $template) { + /** @var \Drupal\webprofiler\DrupalDataCollectorInterface $collector */ + $collector = $profile->getCollector($name); + + if ($collector->hasPanel()) { + $rendered = $template->renderBlock('panel', [ + 'token' => $profile->getToken(), + 'name' => $name, + ]); + + $panels[] = [ + '#theme' => 'webprofiler_panel', + '#panel' => $rendered, + ]; + + $drupalSettings['webprofiler']['collectors'][] = [ + 'id' => $name, + 'name' => $name, + 'label' => $collector->getTitle(), + 'summary' => $collector->getPanelSummary(), + 'icon' => $collector->getIcon(), + ]; + + $libraries = array_merge($libraries, $collector->getLibraries()); + $drupalSettings['webprofiler'] += $collector->getDrupalSettings(); + } + } + + $build = []; + $build['panels'] = [ + '#theme' => 'webprofiler_dashboard', + '#profile' => $profile, + '#panels' => $panels, + '#spinner_path' => '/' . $this->moduleHandler() + ->getModule('webprofiler') + ->getPath() . '/images/searching.gif', + '#attached' => [ + 'drupalSettings' => $drupalSettings, + 'library' => $libraries, + ], + ]; + + return $build; + } + + /** + * Generates the list page. + * + * @param \Symfony\Component\HttpFoundation\Request $request + * + * @return array + */ + public function listAction(Request $request) { + $limit = $request->get('limit', 10); + $this->profiler->disable(); + + $ip = $request->query->get('ip'); + $method = $request->query->get('method'); + $url = $request->query->get('url'); + + $profiles = $this->profiler->find($ip, $url, $limit, $method, '', ''); + + $rows = []; + if (count($profiles)) { + foreach ($profiles as $profile) { + $row = []; + $row[] = Link::fromTextAndUrl($profile['token'], new Url('webprofiler.dashboard', ['profile' => $profile['token']]))->toString(); + $row[] = $profile['ip']; + $row[] = $profile['method']; + $row[] = $profile['url']; + $row[] = $this->date->format($profile['time']); + + $rows[] = $row; + } + } + else { + $rows[] = [ + [ + 'data' => $this->t('No profiles found'), + 'colspan' => 6, + ], + ]; + } + + $build = []; + + $storage_id = $this->config('webprofiler.config')->get('storage'); + $storage = $this->storageManager->getStorage($storage_id); + + $build['resume'] = [ + '#type' => 'inline_template', + '#template' => '

      {{ message }}

      ', + '#context' => [ + 'message' => $this->t('Profiles stored with %storage service.', ['%storage' => $storage['title']]), + ], + ]; + + $build['filters'] = $this->formBuilder() + ->getForm('Drupal\\webprofiler\\Form\\ProfilesFilterForm'); + + $build['table'] = [ + '#type' => 'table', + '#rows' => $rows, + '#header' => [ + $this->t('Token'), + [ + 'data' => $this->t('Ip'), + 'class' => [RESPONSIVE_PRIORITY_LOW], + ], + [ + 'data' => $this->t('Method'), + 'class' => [RESPONSIVE_PRIORITY_LOW], + ], + $this->t('Url'), + [ + 'data' => $this->t('Time'), + 'class' => [RESPONSIVE_PRIORITY_MEDIUM], + ], + ], + '#sticky' => TRUE, + ]; + + return $build; + } + + /** + * Exposes collector's data as JSON. + * + * @param \Symfony\Component\HttpKernel\Profiler\Profile $profile + * @param $collector + * + * @return \Symfony\Component\HttpFoundation\JsonResponse + */ + public function restCollectorAction(Profile $profile, $collector) { + $this->profiler->disable(); + + $data = $profile->getCollector($collector)->getData(); + + return new JsonResponse(['data' => $data]); + } + +} diff --git a/web/modules/contrib/devel/webprofiler/src/Controller/DatabaseController.php b/web/modules/contrib/devel/webprofiler/src/Controller/DatabaseController.php new file mode 100644 index 000000000..d91ef61bf --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/src/Controller/DatabaseController.php @@ -0,0 +1,95 @@ +get('profiler'), + $container->get('database') + ); + } + + /** + * Constructs a new WebprofilerController. + * + * @param \Symfony\Component\HttpKernel\Profiler\Profiler $profiler + * @param \Drupal\Core\Database\Connection $database + */ + public function __construct(Profiler $profiler, Connection $database) { + $this->profiler = $profiler; + $this->database = $database; + } + + /** + * @param \Symfony\Component\HttpKernel\Profiler\Profile $profile + * @param int $qid + * + * @return \Symfony\Component\HttpFoundation\JsonResponse + */ + public function explainAction(Profile $profile, $qid) { + $query = $this->getQuery($profile, $qid); + + $data = []; + $result = $this->database->query('EXPLAIN ' . $query['query'], (array) $query['args']) + ->fetchAllAssoc('table'); + $i = 1; + foreach ($result as $row) { + foreach ($row as $key => $value) { + $data[$i][$key] = $value; + } + $i++; + } + + return new JsonResponse(['data' => $data]); + } + + /** + * @param \Symfony\Component\HttpKernel\Profiler\Profile $profile + * @param int $qid + * + * @return array + */ + private function getQuery(Profile $profile, $qid) { + $this->profiler->disable(); + $token = $profile->getToken(); + + if (!$profile = $this->profiler->loadProfile($token)) { + throw new NotFoundHttpException($this->t('Token @token does not exist.', ['@token' => $token])); + } + + /** @var \Drupal\webprofiler\DataCollector\DatabaseDataCollector $databaseCollector */ + $databaseCollector = $profile->getCollector('database'); + + $queries = $databaseCollector->getQueries(); + $query = $queries[$qid]; + + return $query; + } + +} diff --git a/web/modules/contrib/devel/webprofiler/src/Controller/ToolbarController.php b/web/modules/contrib/devel/webprofiler/src/Controller/ToolbarController.php new file mode 100644 index 000000000..98834d8d7 --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/src/Controller/ToolbarController.php @@ -0,0 +1,111 @@ +get('profiler'), + $container->get('template_manager'), + $container->get('renderer') + ); + } + + /** + * Constructs a new WebprofilerController. + * + * @param \Drupal\webprofiler\Profiler\Profiler $profiler + * @param \Drupal\webprofiler\Profiler\TemplateManager $templateManager + * @param \Drupal\Core\Render\RendererInterface $renderer + */ + public function __construct(Profiler $profiler, TemplateManager $templateManager, RendererInterface $renderer) { + $this->profiler = $profiler; + $this->templateManager = $templateManager; + $this->renderer = $renderer; + } + + /** + * Generates the toolbar. + * + * @param \Symfony\Component\HttpKernel\Profiler\Profile $profile + * + * @return \Symfony\Component\HttpFoundation\Response + * + * @throws \Exception + */ + public function toolbarAction(Profile $profile) { + $this->profiler->disable(); + + $templates = $this->templateManager->getTemplates($profile); + + $rendered = ''; + foreach ($templates as $name => $template) { + $rendered .= $template->renderBlock('toolbar', [ + 'collector' => $profile->getcollector($name), + 'token' => $profile->getToken(), + 'name' => $name, + ]); + } + + $toolbar = [ + '#theme' => 'webprofiler_toolbar', + '#toolbar' => $rendered, + '#token' => $profile->getToken(), + ]; + + return new Response($this->renderer->render($toolbar)); + } + + /** + * @param \Symfony\Component\HttpKernel\Profiler\Profile $profile + * @param \Symfony\Component\HttpFoundation\Request $request + * + * @return \Symfony\Component\HttpFoundation\JsonResponse + */ + public function savePerformanceTimingAction(Profile $profile, Request $request) { + $this->profiler->disable(); + + $data = Json::decode($request->getContent()); + + /** @var $collector */ + $collector = $profile->getCollector('performance_timing'); + $collector->setData($data); + $this->profiler->updateProfile($profile); + + return new JsonResponse(['success' => TRUE]); + } + +} diff --git a/web/modules/contrib/devel/webprofiler/src/DataCollector/AssetsDataCollector.php b/web/modules/contrib/devel/webprofiler/src/DataCollector/AssetsDataCollector.php new file mode 100644 index 000000000..5e71f417b --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/src/DataCollector/AssetsDataCollector.php @@ -0,0 +1,108 @@ +root = $root; + + $this->data['js'] = []; + $this->data['css'] = []; + } + + /** + * {@inheritdoc} + */ + public function collect(Request $request, Response $response, \Exception $exception = NULL) { + $this->data['assets']['installation_path'] = $this->root . '/'; + } + + /** + * @param $jsAsset + */ + public function addJsAsset($jsAsset) { + $this->data['js'] = NestedArray::mergeDeepArray([ + $jsAsset, + $this->data['js'], + ]); + } + + /** + * @param $cssAsset + */ + public function addCssAsset($cssAsset) { + $this->data['css'] = NestedArray::mergeDeepArray([ + $cssAsset, + $this->data['css'], + ]); + } + + /** + * Twig callback to return the amount of CSS files. + */ + public function getCssCount() { + return count($this->data['css']); + } + + /** + * Twig callback to return the amount of JS files. + */ + public function getJsCount() { + return count($this->data['js']) - 1; + } + + /** + * {@inheritdoc} + */ + public function getName() { + return 'assets'; + } + + /** + * {@inheritdoc} + */ + public function getTitle() { + return $this->t('Assets'); + } + + /** + * {@inheritdoc} + */ + public function getPanelSummary() { + return $this->t('Total: @count', ['@count' => ($this->getCssCount() + $this->getJsCount())]); + } + + /** + * {@inheritdoc} + */ + public function getIcon() { + return 'iVBORw0KGgoAAAANSUhEUgAAABUAAAAcCAYAAACOGPReAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyRpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYxIDY0LjE0MDk0OSwgMjAxMC8xMi8wNy0xMDo1NzowMSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNS4xIE1hY2ludG9zaCIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDoxQUE0NEI2NTlCQTkxMUUzQkFDRjg2NUVCQ0NFNTcwQiIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDoxQUE0NEI2NjlCQTkxMUUzQkFDRjg2NUVCQ0NFNTcwQiI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOjFBQTQ0QjYzOUJBOTExRTNCQUNGODY1RUJDQ0U1NzBCIiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOjFBQTQ0QjY0OUJBOTExRTNCQUNGODY1RUJDQ0U1NzBCIi8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+C1mVdgAAAktJREFUeNrUlk+I6VEUx48/4SVKpkgiCywkK8ksWVhYSFm9hYXFqxfbt2KnLJS9hbKQhZXtFMVmUrKYkZmslPxLSvnvYZx37+2NTDPP03ss5tTpcu/xued77rm/Hw4iwqWNC1cwzv8CPlJ6lUypfSf+k256Ad8R/0HlL4l/uWCSL5zfO/xTLTkczrvx9aDwU7QUX61Ww/Pz88WAdrv9Oplyzz0UPp8PIpEIzrnWJ6EUJBAI2DgYDOD+/h7EYjFwuadz4X80SUFCoRB6vR6Mx2Mwm82QTqfh4eEBNBoN3NzcgFQqhdVqBbvd7s+ZUlkUJpFIoN1uQyQSAYvFAqlUiq2Xy2VwuVwQCoXAZrNBMpmE6XTK4nk83lsqOX0ki0h2xUajgYFAAJVKJRoMBoxGo9hqtXA4HCKNq1ar+Pj4iMFgEFUqFer1egyHw9jv93GxWDCOyWTCA5RaLBZjd9jhcGCz2cRXKxQKqNVqcTQaHeZKpRLqdDoWf3d3h6QMB+hB/nK5BL/fD4lEAsiPmUSfzwf1eh1qtRoYjUaQy+WQz+eBbAoej4fVN5PJgNVqZfV9J586lUBtMplgLpdDEoyktuj1ejEej7M1p9OJbrcbi8UirtdrNjebzQ6MN/KPfT6f436/Z3XudDp4e3uLlUqFQej37XbL5B7DjqEfthQBAgGzfpTJZJDNZpn0zWbDRgI/eQlOdjGFU1coFIfHGu3Lv90qbrfbXRFpJ4POAVGjh/r09PRCP38l3r3Q62RA/NtV3qb8T/Nn4irQXwIMANMNuV/Q8qbhAAAAAElFTkSuQmCC'; + } + +} diff --git a/web/modules/contrib/devel/webprofiler/src/DataCollector/BlocksDataCollector.php b/web/modules/contrib/devel/webprofiler/src/DataCollector/BlocksDataCollector.php new file mode 100644 index 000000000..b52dffee1 --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/src/DataCollector/BlocksDataCollector.php @@ -0,0 +1,147 @@ +entityTypeManager = $entity_type_manager; + + $this->data['blocks']['loaded'] = []; + $this->data['blocks']['rendered'] = []; + } + + /** + * {@inheritdoc} + */ + public function collect(Request $request, Response $response, \Exception $exception = NULL) { + $storage = $this->entityTypeManager->getStorage('block'); + $loaded = $this->entityTypeManager->getLoaded('config', 'block'); + $rendered = $this->entityTypeManager->getRendered('block'); + + if ($loaded) { + $this->data['blocks']['loaded'] = $this->getBlocksData($loaded, $storage); + } + + if ($rendered) { + $this->data['blocks']['rendered'] = $this->getBlocksData($rendered, $storage); + } + } + + /** + * @return array + */ + public function getRenderedBlocks() { + return $this->data['blocks']['rendered']; + } + + /** + * @return int + */ + public function getRenderedBlocksCount() { + return count($this->getRenderedBlocks()); + } + + /** + * @return array + */ + public function getLoadedBlocks() { + return $this->data['blocks']['loaded']; + } + + /** + * @return int + */ + public function getLoadedBlocksCount() { + return count($this->getLoadedBlocks()); + } + + /** + * {@inheritdoc} + */ + public function getName() { + return 'blocks'; + } + + /** + * {@inheritdoc} + */ + public function getTitle() { + return $this->t('Blocks'); + } + + /** + * {@inheritdoc} + */ + public function getPanelSummary() { + return $this->t('Loaded: @loaded, rendered: @rendered', [ + '@loaded' => $this->getLoadedBlocksCount(), + '@rendered' => $this->getRenderedBlocksCount(), + ]); + } + + /** + * {@inheritdoc} + */ + public function getIcon() { + return 'iVBORw0KGgoAAAANSUhEUgAAABUAAAAcCAYAAACOGPReAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA2hpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYxIDY0LjE0MDk0OSwgMjAxMC8xMi8wNy0xMDo1NzowMSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDowNDgwMTE3NDA3MjA2ODExOEY2MkNCNjI0NDY3NzkwRCIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDowQjg5OTA4OEYwQTgxMUUzQkJDRThFQjA5Q0E1REFCRCIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDowQjg5OTA4N0YwQTgxMUUzQkJDRThFQjA5Q0E1REFCRCIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ1M1LjEgTWFjaW50b3NoIj4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6MDM4MDExNzQwNzIwNjgxMTg3MUZDQ0I0RjY1RTlEM0IiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6MDQ4MDExNzQwNzIwNjgxMThGNjJDQjYyNDQ2Nzc5MEQiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz68h+kGAAAAWElEQVR42mL8//8/A7UBEwMNwAg3lAVEODg4UCW2Dhw4wDgapgyMtEj8eCMKFPCkyI1GFCJMiUnQVDUUX0SNllK4wxRf+JAdUeQUfaMRRd+ib4SHKUCAAQAMcyf8vLAstgAAAABJRU5ErkJggg=='; + } + + /** + * @param $decorator + * @param $storage + * + * @return array + */ + private function getBlocksData(EntityDecorator $decorator, EntityStorageInterface $storage) { + $blocks = []; + + /** @var \Drupal\block\BlockInterface $block */ + foreach ($decorator->getEntities() as $block) { + /** @var \Drupal\block\Entity\Block $entity */ + if (NULL !== $block && $entity = $storage->load($block->get('id'))) { + + $route = ''; + if ($entity->hasLinkTemplate('edit-form')) { + $route = $entity->toUrl('edit-form')->toString(); + } + + $id = $block->get('id'); + $blocks[$id] = [ + 'id' => $id, + 'region' => $block->getRegion(), + 'status' => $block->get('status'), + 'theme' => $block->getTheme(), + 'plugin' => $block->get('plugin'), + 'settings' => $block->get('settings'), + 'route' => $route, + ]; + } + } + + return $blocks; + } + +} diff --git a/web/modules/contrib/devel/webprofiler/src/DataCollector/CacheDataCollector.php b/web/modules/contrib/devel/webprofiler/src/DataCollector/CacheDataCollector.php new file mode 100644 index 000000000..ae19bb597 --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/src/DataCollector/CacheDataCollector.php @@ -0,0 +1,173 @@ +data['total'][CacheDataCollector::WEBPROFILER_CACHE_HIT] = 0; + $this->data['total'][CacheDataCollector::WEBPROFILER_CACHE_MISS] = 0; + $this->data['cache'] = []; + } + + /** + * Registers a cache get on a specific cache bin. + * + * @param $cache + */ + public function registerCacheHit($bin, $cache) { + $current = isset($this->data['cache'][$bin][$cache->cid]) ? $this->data['cache'][$bin][$cache->cid] : NULL; + + if (!$current) { + $current = $cache; + $current->{CacheDataCollector::WEBPROFILER_CACHE_HIT} = 0; + $current->{CacheDataCollector::WEBPROFILER_CACHE_MISS} = 0; + $this->data['cache'][$bin][$cache->cid] = $current; + } + + $current->{CacheDataCollector::WEBPROFILER_CACHE_HIT}++; + $this->data['total'][CacheDataCollector::WEBPROFILER_CACHE_HIT]++; + } + + /** + * Registers a cache get on a specific cache bin. + * + * @param $bin + * @param $cid + */ + public function registerCacheMiss($bin, $cid) { + $current = isset($this->data['cache'][$bin][$cid]) ? + $this->data['cache'][$bin][$cid] : NULL; + + if (!$current) { + $current = new \StdClass(); + $current->{CacheDataCollector::WEBPROFILER_CACHE_HIT} = 0; + $current->{CacheDataCollector::WEBPROFILER_CACHE_MISS} = 0; + $this->data['cache'][$bin][$cid] = $current; + } + + $current->{CacheDataCollector::WEBPROFILER_CACHE_MISS}++; + $this->data['total'][CacheDataCollector::WEBPROFILER_CACHE_MISS]++; + } + + /** + * Callback to return the total amount of requested cache CIDS. + * + * @param string $type + * + * @return int + */ + public function getCacheCidsCount($type) { + return $this->data['total'][$type]; + } + + /** + * Callback to return the total amount of hit cache CIDS. + * + * @return int + */ + public function getCacheHitsCount() { + return $this->getCacheCidsCount(CacheDataCollector::WEBPROFILER_CACHE_HIT); + } + + /** + * Callback to return the total amount of miss cache CIDS. + * + * @return int + */ + public function getCacheMissesCount() { + return $this->getCacheCidsCount(CacheDataCollector::WEBPROFILER_CACHE_MISS); + } + + /** + * Callback to return the total amount of hit cache CIDs keyed by bin. + * + * @param $type + * + * @return array + */ + public function cacheCids($type) { + $hits = []; + foreach ($this->data['cache'] as $bin => $caches) { + $hits[$bin] = 0; + foreach ($caches as $cache) { + $hits[$bin] += $cache->{$type}; + } + } + + return $hits; + } + + /** + * Callback to return hit cache CIDs keyed by bin. + * + * @return array + */ + public function getCacheHits() { + return $this->cacheCids(CacheDataCollector::WEBPROFILER_CACHE_HIT); + } + + /** + * Callback to return miss cache CIDs keyed by bin. + * + * @return array + */ + public function getCacheMisses() { + return $this->cacheCids(CacheDataCollector::WEBPROFILER_CACHE_MISS); + } + + /** + * {@inheritdoc} + */ + public function getName() { + return 'cache'; + } + + /** + * {@inheritdoc} + */ + public function getTitle() { + return $this->t('Cache'); + } + + /** + * {@inheritdoc} + */ + public function getPanelSummary() { + return $this->t('Hit: @cache_hit, miss: @cache_miss', [ + '@cache_hit' => $this->getCacheCidsCount(CacheDataCollector::WEBPROFILER_CACHE_HIT), + '@cache_miss' => $this->getCacheCidsCount(CacheDataCollector::WEBPROFILER_CACHE_MISS), + ]); + } + + /** + * {@inheritdoc} + */ + public function getIcon() { + return 'iVBORw0KGgoAAAANSUhEUgAAABQAAAAcCAYAAABh2p9gAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyRpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYxIDY0LjE0MDk0OSwgMjAxMC8xMi8wNy0xMDo1NzowMSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNS4xIE1hY2ludG9zaCIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDo2Njc3QTVEQTkxNkMxMUUzQjA3OUEzQTNEMUVGMjVDOCIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDo2Njc3QTVEQjkxNkMxMUUzQjA3OUEzQTNEMUVGMjVDOCI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOjRGQTVBQzYxOTE2QzExRTNCMDc5QTNBM0QxRUYyNUM4IiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOjRGQTVBQzYyOTE2QzExRTNCMDc5QTNBM0QxRUYyNUM4Ii8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+BsBwAAAAAJtJREFUeNpi/P//PwM1ARMDlcEIMdDBweEZjM0IihSgwEx8Gg4cOJCOrhGHOimqe5kF2QVYvDITl0vQvQwTo4oLkS1gQrPpPwiTEBkY6pnwKJ5JyOskJRvkcMUVxjgjhRhDsUUGSQZu3rwZb1j6+voyjhYOI9VAFmKTBTC3oMsTbyAx+RndAqxejo2NJdmL6HoYR6vRwWcgQIABAOn0PsqqgQzcAAAAAElFTkSuQmCC'; + } + +} diff --git a/web/modules/contrib/devel/webprofiler/src/DataCollector/ConfigDataCollector.php b/web/modules/contrib/devel/webprofiler/src/DataCollector/ConfigDataCollector.php new file mode 100644 index 000000000..1798df788 --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/src/DataCollector/ConfigDataCollector.php @@ -0,0 +1,69 @@ +data['config_names'][$name] = isset($this->data['config_names'][$name]) ? $this->data['config_names'][$name] + 1 : 1; + } + + /** + * Callback to display the config names. + */ + public function getConfigNames() { + return $this->data['config_names']; + } + + /** + * {@inheritdoc} + */ + public function getName() { + return 'config'; + } + + /** + * {@inheritdoc} + */ + public function getTitle() { + return $this->t('Config'); + } + + /** + * {@inheritdoc} + */ + public function getPanelSummary() { + return $this->t('Total: @count', ['@count' => count($this->getConfigNames())]); + } + + /** + * {@inheritdoc} + */ + public function getIcon() { + return 'iVBORw0KGgoAAAANSUhEUgAAABsAAAAcCAYAAACQ0cTtAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyRpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYxIDY0LjE0MDk0OSwgMjAxMC8xMi8wNy0xMDo1NzowMSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNS4xIE1hY2ludG9zaCIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDo2Njc3QTVFMjkxNkMxMUUzQjA3OUEzQTNEMUVGMjVDOCIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDo2Njc3QTVFMzkxNkMxMUUzQjA3OUEzQTNEMUVGMjVDOCI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOjY2NzdBNUUwOTE2QzExRTNCMDc5QTNBM0QxRUYyNUM4IiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOjY2NzdBNUUxOTE2QzExRTNCMDc5QTNBM0QxRUYyNUM4Ii8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+f0Re8gAAA3RJREFUeNrsVk9ME1kY/82fTqfQf5ROW2gpuKCioHbdEDeBxITdxE08eOHkiQOr8cZhNx7k4MGDmpAQxTtHPBgPXjCbeLDE/XtglSUGGyhxgBYLdNpSOu3MPN+MwUSQP1HUZONv8st7897M9833fb837zGEEHwusPiM+P86AxjmEqVKSfaBGuWvlHgf6cUUb9+65WhsbPzo706n0/j5wgWddgOUK1vSKEnSvjgyEQwGQW1xtBv+4jXjc7kcBgcH981gJpPZ3tmnjoa7NgOSXwYK2U+bRu76y73XLBaLIRQKvXe8paVlZ0c35L2ncWho6K3B/v5+JBIJOJ3Ot+PmfV9fnzWvcyJWpeNQ/O0oH7oILhK7Q4cnKR9RjlEWrCVtF0Xy/alTW5z19PRgbGwMo6OjlmHToekolUpheHgYhYL1Pso2F+a+OQd/cwTftdlRH9QhujPGconJx5/VyRMvpD+IWrxCa5bmeJ6/GolEtjibmppCuVyGz+dDd3e3ZTwajWJgYMAatyJiBcyGf0J7pwcdzQEcbktBnW8Fr3NMXcOSeLRpKSCw2cD0tNgEQ3+0q0DGx8ettre3FyMjI+/MrYhReKMcgpII1jePdZVBWQXcBxJYfOFGzsjjYGsyErYvdkLJnN1V+hMTE1Zrpm+jv4FVxoemhjISyWqsKi68JDWwcSUsPhWQSQBSdA4Lz+vh92j18rRyZs/rbLMjE0WaTSJlsab+h1LqNES7BqLpYB2vIPwYx/zjNqwlDsBR8VaRwlzzrs42FGlGtgWaBiVZjzLngVChNYQBGATas5OoKCvQSB6k4x64uXYGTwyW322dmao01WgqczNEujMRRYSjqhUOhwAbz4Klu4nB8yhyEtZyPEqh36BquSLYzpltBdLV1WWJwmzN6EzZb4aPUZCfVRHyeiA53Qi6XKhzuxGurUFD8Qz8Xga1BQ+y8SNLEKoe8jupcEOJ26HWSGN5VYYiJ3Ei9i14GpaNezOn8gIcjISn/3Sks8n2PyFqDzhd1y/XeL02URQ/4ExhwFOSMbtux0qxgoDHBb+7GiLPG9l8ce2vyYI8nQjHMXn/Fzy+uWzu1Ofpeze32/D29scVgLpjwMEfSrSVUe1PoUK/Yib+N/69+zuy8gJ9apGxzgcfCkLMmjspq3b4qZsH03XKPPP1kLofeC3AAMaxXzYck+ZeAAAAAElFTkSuQmCC'; + } + +} diff --git a/web/modules/contrib/devel/webprofiler/src/DataCollector/DatabaseDataCollector.php b/web/modules/contrib/devel/webprofiler/src/DataCollector/DatabaseDataCollector.php new file mode 100644 index 000000000..a827c7e09 --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/src/DataCollector/DatabaseDataCollector.php @@ -0,0 +1,266 @@ +database = $database; + $this->configFactory = $configFactory; + } + + /** + * {@inheritdoc} + */ + public function collect(Request $request, Response $response, \Exception $exception = NULL) { + $connections = []; + foreach (Database::getAllConnectionInfo() as $key => $info) { + try { + $database = Database::getConnection('default', $key); + + if ($database->getLogger()) { + $connections[$key] = $database->getLogger()->get('webprofiler'); + } + } + catch (\Exception $e) { + // There was some error during database connection, maybe a stale + // configuration in settings.php or wrong values used for a migration. + } + } + + $this->data['connections'] = array_keys($connections); + + $data = []; + foreach ($connections as $key => $queries) { + foreach ($queries as $query) { + // Remove caller args. + unset($query['caller']['args']); + + // Remove query args element if empty. + if (isset($query['args']) && empty($query['args'])) { + unset($query['args']); + } + + // Save time in milliseconds. + $query['time'] = $query['time'] * 1000; + $query['database'] = $key; + $data[] = $query; + } + } + + $querySort = $this->configFactory->get('webprofiler.config') + ->get('query_sort'); + if ('duration' === $querySort) { + usort( + $data, [ + "Drupal\\webprofiler\\DataCollector\\DatabaseDataCollector", + "orderQueryByTime", + ] + ); + } + + $this->data['queries'] = $data; + + $options = $this->database->getConnectionOptions(); + + // Remove password for security. + unset($options['password']); + + $this->data['database'] = $options; + } + + /** + * @return array + */ + public function getDatabase() { + return $this->data['database']; + } + + /** + * @return int + */ + public function getQueryCount() { + return count($this->data['queries']); + } + + /** + * @return array + */ + public function getQueries() { + return $this->data['queries']; + } + + /** + * Returns the total execution time. + * + * @return float + */ + public function getTime() { + $time = 0; + + foreach ($this->data['queries'] as $query) { + $time += $query['time']; + } + + return $time; + } + + /** + * Returns a color based on the number of executed queries. + * + * @return string + */ + public function getColorCode() { + if ($this->getQueryCount() < 100) { + return 'green'; + } + if ($this->getQueryCount() < 200) { + return 'yellow'; + } + + return 'red'; + } + + /** + * Returns the configured query highlight threshold. + * + * @return int + */ + public function getQueryHighlightThreshold() { + // When a profile is loaded from storage this object is deserialized and + // no constructor is called so we cannot use dependency injection. + return \Drupal::config('webprofiler.config')->get('query_highlight'); + } + + /** + * {@inheritdoc} + */ + public function getName() { + return 'database'; + } + + /** + * {@inheritdoc} + */ + public function getTitle() { + return $this->t('Database'); + } + + /** + * {@inheritdoc} + */ + public function getPanelSummary() { + return $this->t('Executed queries: @count', ['@count' => $this->getQueryCount()]); + } + + /** + * {@inheritdoc} + */ + public function getIcon() { + return 'iVBORw0KGgoAAAANSUhEUgAAABQAAAAcCAYAAABh2p9gAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAQRJREFUeNpi/P//PwM1ARMDlcGogZQDlpMnT7pxc3NbA9nhQKxOpL5rQLwJiPeBsI6Ozl+YBOOOHTv+AOllQNwtLS39F2owKYZ/gRq8G4i3ggxEToggWzvc3d2Pk+1lNL4fFAs6ODi8JzdS7mMRVyDVoAMHDsANdAPiOCC+jCQvQKqBQB/BDbwBxK5AHA3E/kB8nKJkA8TMQBwLxaBIKQbi70AvTADSBiSadwFXpCikpKQU8PDwkGTaly9fHFigkaKIJid4584dkiMFFI6jkTJII0WVmpHCAixZQEXWYhDeuXMnyLsVlEQKI45qFBQZ8eRECi4DBaAlDqle/8A48ip6gAADANdQY88Uc0oGAAAAAElFTkSuQmCC'; + } + + /** + * {@inheritdoc} + */ + public function getLibraries() { + return [ + 'webprofiler/database', + ]; + } + + /** + * {@inheritdoc} + */ + public function getData() { + $data = $this->data; + + $conn = Database::getConnection(); + foreach ($data['queries'] as &$query) { + $explain = TRUE; + $type = 'select'; + + if (strpos($query['query'], 'INSERT') !== FALSE) { + $explain = FALSE; + $type = 'insert'; + } + + if (strpos($query['query'], 'UPDATE') !== FALSE) { + $explain = FALSE; + $type = 'update'; + } + + if (strpos($query['query'], 'CREATE') !== FALSE) { + $explain = FALSE; + $type = 'create'; + } + + if (strpos($query['query'], 'DELETE') !== FALSE) { + $explain = FALSE; + $type = 'delete'; + } + + $query['explain'] = $explain; + $query['type'] = $type; + + $quoted = []; + + if (isset($query['args'])) { + foreach ((array) $query['args'] as $key => $val) { + $quoted[$key] = is_null($val) ? 'NULL' : $conn->quote($val); + } + } + + $query['query_args'] = strtr($query['query'], $quoted); + } + + $data['query_highlight_threshold'] = $this->getQueryHighlightThreshold(); + + return $data; + } + + /** + * @param $a + * @param $b + * + * @return int + */ + private function orderQueryByTime($a, $b) { + $at = $a['time']; + $bt = $b['time']; + + if ($at == $bt) { + return 0; + } + return ($at < $bt) ? 1 : -1; + } + +} diff --git a/web/modules/contrib/devel/webprofiler/src/DataCollector/DevelDataCollector.php b/web/modules/contrib/devel/webprofiler/src/DataCollector/DevelDataCollector.php new file mode 100644 index 000000000..eb0c5c216 --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/src/DataCollector/DevelDataCollector.php @@ -0,0 +1,103 @@ +data['original_url'] = $request->getPathInfo(); + } + + /** + * @return string + */ + public function getLinks() { + return $this->develMenuLinks($this->data['original_url']); + } + + /** + * {@inheritdoc} + */ + public function getName() { + return 'devel'; + } + + /** + * {@inheritdoc} + */ + public function getTitle() { + return $this->t('Development'); + } + + /** + * {@inheritdoc} + */ + public function hasPanel() { + return FALSE; + } + + /** + * Returns the collector icon in base64 format. + * + * @return string + * The collector icon. + */ + public function getIcon() { + return 'iVBORw0KGgoAAAANSUhEUgAAABYAAAAcCAYAAABlL09dAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyRpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYxIDY0LjE0MDk0OSwgMjAxMC8xMi8wNy0xMDo1NzowMSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNS4xIE1hY2ludG9zaCIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDo2RTdCREU2NUVFQUUxMUU1QTc4Q0Q0OEU5RUY0N0YwMyIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDo2RTdCREU2NkVFQUUxMUU1QTc4Q0Q0OEU5RUY0N0YwMyI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOjZFN0JERTYzRUVBRTExRTVBNzhDRDQ4RTlFRjQ3RjAzIiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOjZFN0JERTY0RUVBRTExRTVBNzhDRDQ4RTlFRjQ3RjAzIi8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+6sNOKAAAAPVJREFUeNpi/P//PwMtABMDjcCowbQ3mNHe3h6XnBUQTwJiTRzy14E4+8CBAyexSbLgsZQbiI2BOBCH/HqoGgZSDYaBDXjk/uMzuBaLzeVI7FV4DN7n4OAAojvRxL+CwvgnkMFG5bj7BUoVv4lUfBWIrxGp9jcoKM4DsQABhZ+A2BPqs61AzEVA/XuQwT1AzElA4Q8g/gJN951EBN13kMFLgJiHgMJvQCwNxOxAvJwIg7+wEBnGv4D4M1TtLyIMBocxMQUyKExnAzEzEHMQof4/C5GxDHJh4qAp3VhpYC4rKCieAbEQlQ1+yzhamY4aTD+DAQIMAFv+MFaJEyYhAAAAAElFTkSuQmCC'; + } + + /** + * @param string $original_url + * + * @return array Array containing Devel Menu links + * Array containing Devel Menu links + */ + protected function develMenuLinks($original_url) { + // We cannot use injected services here because at this point this + // class is deserialized from a storage and not constructed. + $menuLinkTreeService = \Drupal::service('menu.link_tree'); + $rendererService = \Drupal::service('renderer'); + + $parameters = new MenuTreeParameters(); + $parameters->setMaxDepth(1)->onlyEnabledLinks(); + $tree = $menuLinkTreeService->load('devel', $parameters); + + $manipulators = [ + ['callable' => 'menu.default_tree_manipulators:checkAccess'], + ['callable' => 'menu.default_tree_manipulators:generateIndexAndSort'], + ]; + $tree = $menuLinkTreeService->transform($tree, $manipulators); + + $links = []; + + foreach ($tree as $item) { + /** @var \Drupal\devel\Plugin\Menu\DestinationMenuLink $link */ + $link = $item->link; + $renderable = Link::fromTextAndUrl($link->getTitle(), $link->getUrlObject()) + ->toRenderable(); + $rendered = $rendererService->renderPlain($renderable); + $linkString = preg_replace('/\/profiler\/(.*)&/', $original_url . '&', $rendered); + $links[] = Markup::create($linkString);; + } + + return $links; + } + +} diff --git a/web/modules/contrib/devel/webprofiler/src/DataCollector/DrupalDataCollector.php b/web/modules/contrib/devel/webprofiler/src/DataCollector/DrupalDataCollector.php new file mode 100644 index 000000000..e018a2aa7 --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/src/DataCollector/DrupalDataCollector.php @@ -0,0 +1,139 @@ +redirectDestination = $redirectDestination; + $this->urlGenerator = $urlGenerator; + } + + /** + * {@inheritdoc} + */ + public function collect(Request $request, Response $response, \Exception $exception = NULL) { + $this->data['version'] = Drupal::VERSION; + $this->data['profile'] = \Drupal::installProfile(); + $this->data['config_url'] = (new Url('webprofiler.settings', [], ['query' => $this->redirectDestination->getAsArray()]))->toString(); + + try { + $process = new Process("git log -1 --pretty=format:'%H - %s (%ci)' --abbrev-commit"); + $process->setTimeout(3600); + $process->mustRun(); + $this->data['git_commit'] = $process->getOutput(); + + $process = new Process("git log -1 --pretty=format:'%h' --abbrev-commit"); + $process->setTimeout(3600); + $process->mustRun(); + $this->data['abbr_git_commit'] = $process->getOutput(); + } + catch (ProcessFailedException $e) { + $this->data['git_commit'] = $this->data['git_commit_abbr'] = NULL; + } + catch (RuntimeException $e) { + $this->data['git_commit'] = $this->data['git_commit_abbr'] = NULL; + } + } + + /** + * @return string + */ + public function getVersion() { + return $this->data['version']; + } + + /** + * @return string + */ + public function getProfile() { + return $this->data['profile']; + } + + /** + * @return string + */ + public function getConfigUrl() { + return $this->data['config_url']; + } + + /** + * @return string + */ + public function getGitCommit() { + return $this->data['git_commit']; + } + + /** + * @return string + */ + public function getAbbrGitCommit() { + return $this->data['abbr_git_commit']; + } + + /** + * Returns the name of the collector. + * + * @return string + * The collector name + * + * @api + */ + public function getName() { + return 'drupal'; + } + + /** + * {@inheritdoc} + */ + public function getTitle() { + return $this->t('Drupal'); + } + + /** + * {@inheritdoc} + */ + public function hasPanel() { + return FALSE; + } + + /** + * {@inheritdoc} + */ + public function getIcon() { + return 'iVBORw0KGgoAAAANSUhEUgAAABwAAAAcCAYAAAByDd+UAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyRpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYxIDY0LjE0MDk0OSwgMjAxMC8xMi8wNy0xMDo1NzowMSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNS4xIE1hY2ludG9zaCIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDpCRDU5MUVDNzVGRTkxMUUzODdBMEJDOEZFRjA2QUY5MiIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDpCRDU5MUVDODVGRTkxMUUzODdBMEJDOEZFRjA2QUY5MiI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOkJENTkxRUM1NUZFOTExRTM4N0EwQkM4RkVGMDZBRjkyIiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOkJENTkxRUM2NUZFOTExRTM4N0EwQkM4RkVGMDZBRjkyIi8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+iYsuIgAAAiNJREFUeNq8lj3KwkAQhhOxUPAnoIgWQiysbFLYG2/gEVJaegQ9QfQECh4g3kA9QbxBLMQ2KaxE2G/ffG5Yl/ytiguDJBn32Z15d2dUQojy0wGgDHQwGFjj8Vh7hxFyZIHNZtOt1+uuDPRtYK/Xs4rFIpyJDFQaiIm73a7NYMyq1arf7/dnWWApIN3VHBPzINHwHX5J4FxAKg6j0Wh4aSDREGb8TxqIXJVKJSIDY1Yul30RmgqE87swHkrDq2cCkYNareZ9AmPWarX2mUAk/hswZrgkUoGyIsm7y1ggDachnrNPrVKpEKSJBxZYnC+Xi/l4PL56T99uN/y8KDYCUpj2i2JRzONkWZYyGo0UXdeV4/GobDYb5Xw+J/obxv+mTqdT8rWTpND1ek3ihm3bRNO0F188O44T+UwmE+TQjBXNcDiciLD5fE7Shuu6xDTN0JdGgfi+//J9v98nA6EmqIoHYsJPxuFwIOKxiERDVxNQ4I4PNw1Ram6DIFAWiwUWG5uv+/2+E98V+IdOp7OiZzF6pitMhC2XyxDEfOKg1+t1ldnTtNtthxcB8sAGcgQRUbWGufM8LxQPfJBv3n+73Tq5qgVyibqWdYsASOUfLYyJJ679yKyHadDZbBYqkn8HMHaO3ydMl674rI8RayOACKUo/+l0SuAf12ZI9TRYLSZCJYk76GgbnyA9TxOlMpiqqrnuQhxk7igd8raJbKi/bvX/BBgANANSieJk+XMAAAAASUVORK5CYII='; + } + +} diff --git a/web/modules/contrib/devel/webprofiler/src/DataCollector/DrupalDataCollectorTrait.php b/web/modules/contrib/devel/webprofiler/src/DataCollector/DrupalDataCollectorTrait.php new file mode 100644 index 000000000..9fda523ff --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/src/DataCollector/DrupalDataCollectorTrait.php @@ -0,0 +1,122 @@ +data = []; + } + + /** + * {@inheritdoc} + */ + public function getPanelSummary() { + return NULL; + } + + /** + * {@inheritdoc} + */ + public function hasPanel() { + return TRUE; + } + + /** + * {@inheritdoc} + */ + public function getLibraries() { + return []; + } + + /** + * {@inheritdoc} + */ + public function getDrupalSettings() { + return []; + } + + /** + * @return mixed + */ + public function getData() { + return $this->data; + } + + /** + * @param $class + * @param $method + * + * @return array + */ + public function getMethodData($class, $method) { + $class = is_object($class) ? get_class($class) : $class; + $data = []; + + try { + $reflectedMethod = new \ReflectionMethod($class, $method); + + $data = [ + 'class' => $class, + 'method' => $method, + 'file' => $reflectedMethod->getFilename(), + 'line' => $reflectedMethod->getStartLine(), + ]; + } + catch (\ReflectionException $re) { + // TODO: handle the exception. + } finally { + return $data; + } + } + + /** + * @param $value + * + * @return int|string + */ + private function convertToBytes($value) { + if ('-1' === $value) { + return -1; + } + + $value = strtolower($value); + $max = strtolower(ltrim($value, '+')); + if (0 === strpos($max, '0x')) { + $max = intval($max, 16); + } + elseif (0 === strpos($max, '0')) { + $max = intval($max, 8); + } + else { + $max = intval($max); + } + + switch (substr($value, -1)) { + case 't': + $max *= 1024; + break; + + case 'g': + $max *= 1024; + break; + + case 'm': + $max *= 1024; + break; + + case 'k': + $max *= 1024; + break; + } + + return $max; + } + +} diff --git a/web/modules/contrib/devel/webprofiler/src/DataCollector/EventsDataCollector.php b/web/modules/contrib/devel/webprofiler/src/DataCollector/EventsDataCollector.php new file mode 100644 index 000000000..c5385df8a --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/src/DataCollector/EventsDataCollector.php @@ -0,0 +1,145 @@ +eventDispatcher = $event_dispatcher; + } + + /** + * {@inheritdoc} + */ + public function collect(Request $request, Response $response, \Exception $exception = NULL) { + $this->data = [ + 'called_listeners' => [], + 'called_listeners_count' => 0, + 'not_called_listeners' => [], + 'not_called_listeners_count' => 0, + ]; + } + + /** + * {@inheritdoc} + */ + public function lateCollect() { + if ($this->eventDispatcher instanceof EventDispatcherTraceableInterface) { + $countCalled = 0; + $calledListeners = $this->eventDispatcher->getCalledListeners(); + foreach ($calledListeners as &$events) { + foreach ($events as &$priority) { + foreach ($priority as &$listener) { + $countCalled++; + $listener['clazz'] = $this->getMethodData($listener['class'], $listener['method']); + } + } + } + + $countNotCalled = 0; + $notCalledListeners = $this->eventDispatcher->getNotCalledListeners(); + foreach ($notCalledListeners as $events) { + foreach ($events as $priority) { + foreach ($priority as $listener) { + $countNotCalled++; + } + } + } + + $this->data = [ + 'called_listeners' => $calledListeners, + 'called_listeners_count' => $countCalled, + 'not_called_listeners' => $notCalledListeners, + 'not_called_listeners_count' => $countNotCalled, + ]; + } + } + + /** + * @return array + */ + public function getCalledListeners() { + return $this->data['called_listeners']; + } + + /** + * @return array + */ + public function getNotCalledListeners() { + return $this->data['not_called_listeners']; + } + + /** + * @return int + */ + public function getCalledListenersCount() { + return $this->data['called_listeners_count']; + } + + /** + * @return int + */ + public function getNotCalledListenersCount() { + return $this->data['not_called_listeners_count']; + } + + /** + * {@inheritdoc} + */ + public function getName() { + return 'events'; + } + + /** + * @return mixed + */ + public function getData() { + return $this->data; + } + + /** + * {@inheritdoc} + */ + public function getTitle() { + return $this->t('Events'); + } + + /** + * {@inheritdoc} + */ + public function getPanelSummary() { + return $this->t('Called listeners: @listeners', ['@listeners' => $this->getCalledListenersCount()]); + } + + /** + * {@inheritdoc} + */ + public function getIcon() { + return 'iVBORw0KGgoAAAANSUhEUgAAABUAAAAcCAYAAACOGPReAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAABFJJREFUeNrkVVlIY2cY/RMTE81NMkkajUs1OBqkiVsjjAtStGrtSGyFjOjAQNVCKRb66ot9KrjgQx+FUgTBKkURbIfighWl4r6h44pajcZEo3ESTeKS9PzB2AyNZaD1qRcOem+S83/f+c53Lsvj8ZD/+mKTB7gehJTj+2d9fZ1MTk6S0NBQSW9vb97e3t7jmpqaXzIzM185HA7vd4KDg8nGxoaysbGxVCwWm/V6/aDL5TKlpKSQpKSkv5NyuVxyc3Mj7e7u/jw2NjYxJyfnMDIykmGz2UQgEBAWi0XcbjeRSqWhZWVl4v39fXVXV5cqNzf3exxmCNj+9fU1MzQ09JVWq32sUqmMu7u7QhwiDwoKIoeHh2R7e5twOByCwcrQhUShUJjz8vJkw8PDX5+fn8sDkvb3938YHR39rlAoNBoMBgGqtWxubnJRKbu9vZ20trZSQoJnvKioKMvZ2Rn/6urKmpqayvT19ekCks7NzaUnJyeboK0kPj7+cGZmJprH4zGnp6duEBFUTg4ODqjmIfPz87GQxoRnori4ODOKUPuTsnw+RRvPGIYJMZvNDNplYmJiLvPz839oamoSj4yMfAJNuRqN5mV9ff0fOPDF1NSUAt85lclkDkjnys7O/vGOlZLeQgjIgUggnmqHqmMqKip+z8jI8MAFnpKSkpXZ2dn38JkIUAFRQNjt/R2Xv09twBFwAGwClunp6efLy8tZdFgUW1tbiaOjo1/is9fUhcA+YL69fzvzSyQSEQZHfBJBT4J2Bf9qo9Rq9bxcLndeXl4STJrA8B4Mc/atN4pesAk5OTkh1PB0exYXF/kWi4UTFhZG+Hw+wZQJ5BDR7fEPIroYASu9uLggJpOJYO2I0+kkqI47Njb2MdzAKS4uXisvL5/FurIGBgaeYoDS1dVVsrKyQpaWlghsF7hS2IJERER4T4U/qckT4ccP6BYplco+rOcxqn0fZFqj0fgkLS3tV18m0EICktJV9F101xcWFj5Cu+HQ1YGNoeSXWGErpv8IwVOSlZXVh7xw0zy4V1MY3/uXWgetMzB8EZUHw7lKSEjgQ0MONLei2kcTExN5R0dHMehshw7x3umLRKI7YDhaDOSJ18hstq2qquobLMG30DKYkuzs7KggTa5Pf4p/rJReSCud1WplEBYuSMGrra39FG1ywsPDgwsLC+0YFoMAKi0qKupA5c57K0V1XjsdHx+/g6mXUksVFBS8wmF23FeMj48/w7PXiLsxePcG65qPDNCsra15XRCQFNP1AgRPMaA4aOvp6OjQ2O12cVtb20vE389YAHFLS0sO2vbYbLYQHKRHShEEy5ul+kIAe02Q5vy6urouTNyDV8VNT0/PBGzzxW1wRIHsM7T+W3V1tROvEE9lZeUCKlVgSfyD6S9SGsKdnZ1pOp3OkJ6efj04OPgTnmsAlv8PACXa/Q4L4UByuZqbm/UNDQ1vkLL+3+/9ByH9U4ABADscgvUMKuLiAAAAAElFTkSuQmCC'; + } + +} diff --git a/web/modules/contrib/devel/webprofiler/src/DataCollector/ExtensionDataCollector.php b/web/modules/contrib/devel/webprofiler/src/DataCollector/ExtensionDataCollector.php new file mode 100644 index 000000000..f8a6a659c --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/src/DataCollector/ExtensionDataCollector.php @@ -0,0 +1,157 @@ +moduleHandler = $module_handler; + $this->themeHandler = $theme_handler; + $this->root = $root; + + $this->data['drupal_extension']['modules'] = []; + $this->data['drupal_extension']['themes'] = []; + } + + /** + * {@inheritdoc} + */ + public function collect(Request $request, Response $response, \Exception $exception = NULL) { + $modules = $this->moduleHandler->getModuleList(); + $themes = $this->themeHandler->listInfo(); + + $this->data['drupal_extension']['count'] = count($modules) + count($themes); + $this->data['drupal_extension']['modules'] = $modules; + $this->data['drupal_extension']['themes'] = $themes; + $this->data['drupal_extension']['installation_path'] = $this->root . '/'; + } + + /** + * Returns the total number of active extensions. + * + * @return int + */ + public function getExtensionsCount() { + return isset($this->data['drupal_extension']['count']) ? $this->data['drupal_extension']['count'] : 0; + } + + /** + * Returns the total number of active modules. + * + * @return int + */ + public function getModulesCount() { + return count($this->data['drupal_extension']['modules']); + } + + /** + * Returns the total number of active themes. + * + * @return int + */ + public function getThemesCount() { + return count($this->data['drupal_extension']['themes']); + } + + /** + * {@inheritdoc} + */ + public function getName() { + return 'drupal_extension'; + } + + /** + * {@inheritdoc} + */ + public function getTitle() { + return $this->t('Extensions'); + } + + /** + * {@inheritdoc} + */ + public function getPanelSummary() { + return $this->t('Total: @extensions', ['@extensions' => $this->getExtensionsCount()]); + } + + /** + * {@inheritdoc} + */ + public function getIcon() { + return 'iVBORw0KGgoAAAANSUhEUgAAABQAAAAcCAYAAABh2p9gAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyRpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYxIDY0LjE0MDk0OSwgMjAxMC8xMi8wNy0xMDo1NzowMSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNS4xIE1hY2ludG9zaCIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDo0RkE1QUM1RjkxNkMxMUUzQjA3OUEzQTNEMUVGMjVDOCIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDo0RkE1QUM2MDkxNkMxMUUzQjA3OUEzQTNEMUVGMjVDOCI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOjRGQTVBQzVEOTE2QzExRTNCMDc5QTNBM0QxRUYyNUM4IiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOjRGQTVBQzVFOTE2QzExRTNCMDc5QTNBM0QxRUYyNUM4Ii8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+7LqYtwAAAUBJREFUeNpi/P//PwM1ARMDlcGogYPQQBYQ4eDgQIza/SClQNwIxA3okgcOHCDZhRVAfA6I64F4EjW8fBLqwn1AnAvE8aQaqAc1QAZJ7DMQRwPxayDuAGIBYgwUAeL1QHwWGm6XgLgUSf4FEPcAsQQQ+xBjICh8AmARBgSCQNwFxEFIatYCMagQcEHXzGhvb09s6bAdiL2gbHYg/oEm/x8Y00ykRAofEvsvzlgGmsoIpIWBWBaIlYD4Bg61B5DY/ED8HRrjIH2yINchh+E7IH4CxPeBuAAam8jgBBD3IfEtgZgTiPdC9T1BySloYCcQ20DTmTg0tuegWVICjZSNWLMeFgBKKsU45EDi9kA8H4ivEmsgNsAKxEXQBH0TiMtwFg5EgvnQXHIFmibfUGrgeaghTdBIxAoYR2u9EWAgQIABAKKeRzEX0gXIAAAAAElFTkSuQmCC'; + } + + /** + * {@inheritdoc} + */ + public function getData() { + $data = $this->data; + + // Copy protected properties over public ones to + // let json_encode to find them. + $this->copyToPublic($data['drupal_extension']['modules']); + $this->copyToPublic($data['drupal_extension']['themes']); + + return $data; + } + + /** + * Copies protected properties to public ones. + * + * @param \Drupal\Core\Extension\Extension[] $extensions + */ + private function copyToPublic($extensions) { + foreach ($extensions as &$extension) { + $extension->public_type = $extension->getType(); + $extension->public_name = $extension->getName(); + $extension->public_path = $extension->getPath(); + $extension->public_pathname = $extension->getPathname(); + $extension->public_filename = $extension->getFilename(); + $extension->public_extension_pathname = $extension->getExtensionPathname(); + $extension->public_extension_filename = $extension->getExtensionFilename(); + } + } + +} diff --git a/web/modules/contrib/devel/webprofiler/src/DataCollector/FormsDataCollector.php b/web/modules/contrib/devel/webprofiler/src/DataCollector/FormsDataCollector.php new file mode 100644 index 000000000..d5e866d62 --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/src/DataCollector/FormsDataCollector.php @@ -0,0 +1,87 @@ +formBuilder = $formBuilder; + + $this->data['forms'] = []; + } + + /** + * {@inheritdoc} + */ + public function collect(Request $request, Response $response, \Exception $exception = NULL) { + $this->data['forms'] = []; + + if ($this->formBuilder instanceof FormBuilderWrapper) { + $this->data['forms'] = $this->formBuilder->getBuildForm(); + } + } + + /** + * @return array + */ + public function getForms() { + return (!empty($this->data['forms']) && is_array($this->data['forms'])) ? $this->data['forms'] : []; + } + + /** + * @return int + */ + public function getFormsCount() { + return count($this->getForms()); + } + + /** + * {@inheritdoc} + */ + public function getName() { + return 'forms'; + } + + /** + * {@inheritdoc} + */ + public function getTitle() { + return $this->t('Forms'); + } + + /** + * {@inheritdoc} + */ + public function getPanelSummary() { + return $this->t('Rendered: @forms', ['@forms' => $this->getFormsCount()]); + } + + /** + * {@inheritdoc} + */ + public function getIcon() { + return 'iVBORw0KGgoAAAANSUhEUgAAABQAAAAcCAYAAABh2p9gAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyRpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYxIDY0LjE0MDk0OSwgMjAxMC8xMi8wNy0xMDo1NzowMSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNS4xIE1hY2ludG9zaCIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDo0RkE1QUM1QjkxNkMxMUUzQjA3OUEzQTNEMUVGMjVDOCIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDo0RkE1QUM1QzkxNkMxMUUzQjA3OUEzQTNEMUVGMjVDOCI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOjRGQTVBQzU5OTE2QzExRTNCMDc5QTNBM0QxRUYyNUM4IiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOjRGQTVBQzVBOTE2QzExRTNCMDc5QTNBM0QxRUYyNUM4Ii8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+I7130QAAAKRJREFUeNrsVVsKxCAMzIgH8897eDHxGP55LX+0RmjpYxHdyrLsNl9iknEyExA5Z5oZgibH9wPKVlJrnVNKRwZCkPceQ4AhhOpUjJFaeaXUBRjssrW2FvDZGINS2GV9AQb3rpvCvZWhc24rKpdDmp17P2MKjzIFEAD16vdDi73Xbx2/pelZItnz6oiusneB32b4is0Iw8eUfzBlOiCef/l2LAIMAGACWQCJ5bXFAAAAAElFTkSuQmCC'; + } + +} diff --git a/web/modules/contrib/devel/webprofiler/src/DataCollector/HttpDataCollector.php b/web/modules/contrib/devel/webprofiler/src/DataCollector/HttpDataCollector.php new file mode 100644 index 000000000..461b3bddd --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/src/DataCollector/HttpDataCollector.php @@ -0,0 +1,176 @@ +middleware = $middleware; + + $this->data['completed'] = []; + $this->data['failed'] = []; + } + + /** + * {@inheritdoc} + */ + public function collect(Request $request, Response $response, \Exception $exception = NULL) { + $completed = $this->middleware->getCompletedRequests(); + $failed = $this->middleware->getFailedRequests(); + + foreach ($completed as $data) { + /** @var \GuzzleHttp\Psr7\Request $request */ + $request = $data['request']; + /** @var \GuzzleHttp\Psr7\Response $response */ + $response = $data['response']; + /** @var \GuzzleHttp\TransferStats $stats */ + $stats = $request->stats; + + $uri = $request->getUri(); + $this->data['completed'][] = [ + 'request' => [ + 'method' => $request->getMethod(), + 'uri' => [ + 'schema' => $uri->getScheme(), + 'host' => $uri->getHost(), + 'port' => $uri->getPort(), + 'path' => $uri->getPath(), + 'query' => $uri->getQuery(), + 'fragment' => $uri->getFragment(), + ], + 'headers' => $request->getHeaders(), + 'protocol' => $request->getProtocolVersion(), + 'request_target' => $request->getRequestTarget(), + 'stats' => [ + 'transferTime' => $stats->getTransferTime(), + 'handlerStats' => $stats->getHandlerStats(), + ], + ], + 'response' => [ + 'phrase' => $response->getReasonPhrase(), + 'status' => $response->getStatusCode(), + 'headers' => $response->getHeaders(), + 'protocol' => $response->getProtocolVersion(), + ], + ]; + } + + foreach ($failed as $data) { + /** @var \GuzzleHttp\Psr7\Request $request */ + $request = $data['request']; + /** @var \GuzzleHttp\Psr7\Response $response */ + $response = $data['response']; + + $uri = $request->getUri(); + $failureData = [ + 'request' => [ + 'method' => $request->getMethod(), + 'uri' => [ + 'schema' => $uri->getScheme(), + 'host' => $uri->getHost(), + 'port' => $uri->getPort(), + 'path' => $uri->getPath(), + 'query' => $uri->getQuery(), + 'fragment' => $uri->getFragment(), + ], + 'headers' => $request->getHeaders(), + 'protocol' => $request->getProtocolVersion(), + 'request_target' => $request->getRequestTarget(), + ], + ]; + + if ($response) { + $failureData['response'] = [ + 'phrase' => $response->getReasonPhrase(), + 'status' => $response->getStatusCode(), + 'headers' => $response->getHeaders(), + 'protocol' => $response->getProtocolVersion(), + ]; + } + + $this->data['failed'][] = $failureData; + } + } + + /** + * @return int + */ + public function getCompletedRequestsCount() { + return count($this->getCompletedRequests()); + } + + /** + * @return array + */ + public function getCompletedRequests() { + return $this->data['completed']; + } + + /** + * @return int + */ + public function getFailedRequestsCount() { + return count($this->getFailedRequests()); + } + + /** + * @return array + */ + public function getFailedRequests() { + return $this->data['failed']; + } + + /** + * {@inheritdoc} + */ + public function getName() { + return 'http'; + } + + /** + * {@inheritdoc} + */ + public function getTitle() { + return $this->t('Http'); + } + + /** + * {@inheritdoc} + */ + public function getPanelSummary() { + return $this->t( + 'Completed @completed, error @error', [ + '@completed' => $this->getCompletedRequestsCount(), + '@error' => $this->getFailedRequestsCount(), + ] + ); + } + + /** + * {@inheritdoc} + */ + public function getIcon() { + return 'iVBORw0KGgoAAAANSUhEUgAAABUAAAAcCAYAAACOGPReAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAATlJREFUeNrsleERgjAMha0TdISOwAZ2BEZghI7ABo7AOQFuwAjoBLgBblBbfb0LudByp/4jdw+PNnxtkqYq7/3h13Y8/MF26B9spfo6yAX1QSa6EY2Y0xLrzROgddAMOcgLmuFbhDqyG00W8Rm1JWgEWCG0oQBuJKjGigNUs/xWUJdJheZQJzhZFGkgKWkw1mM8bmTCvOPQcSVXrTA+Ydc0kujXJGg6p5VwrG5BJzb2CLriN9kT74afUylPloQ+kdDPELXpg1qGvwbtURwjFGkkC8RIZw6d2QeO7MII81wRbDm0Z068Zf0G1bxQl8z1UH1zoQwk9NRZdkPoHt+KbZoqa+HYZS4T3iimdEvRDrIF4KIRclBNjk+uSB2/eBJUvR9KSek26BZHOit2zl3oqkV91P6//3N7CTAAIIc/qj2gy4gAAAAASUVORK5CYII='; + } + +} diff --git a/web/modules/contrib/devel/webprofiler/src/DataCollector/MailDataCollector.php b/web/modules/contrib/devel/webprofiler/src/DataCollector/MailDataCollector.php new file mode 100644 index 000000000..39899929e --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/src/DataCollector/MailDataCollector.php @@ -0,0 +1,103 @@ +messages = []; + } + + /** + * Collects data for the given Request and Response. + * + * @param \Symfony\Component\HttpFoundation\Request $request + * A Request instance. + * @param \Symfony\Component\HttpFoundation\Response $response + * A Response instance. + * @param \Exception $exception + * An Exception instance. + * + * @api + */ + public function collect(Request $request, Response $response, \Exception $exception = NULL) { + $this->data['mail'] = $this->messages; + } + + /** + * @param $message + * @param \Drupal\Core\Mail\MailInterface $mail + */ + public function addMessage($message, MailInterface $mail) { + $class = get_class($mail); + $method = $this->getMethodData($class, 'mail'); + + $this->messages[] = [ + 'message' => $message, + 'method' => $method, + ]; + } + + /** + * @return int + */ + public function getMailSent() { + return count($this->data['mail']); + } + + /** + * Returns the name of the collector. + * + * @return string The collector name + * + * @api + */ + public function getName() { + return 'mail'; + } + + /** + * Returns the datacollector title. + * + * @return string + * The datacollector title. + */ + public function getTitle() { + return $this->t('Mail'); + } + + /** + * {@inheritdoc} + */ + public function getPanelSummary() { + return $this->t('Total: @count', ['@count' => $this->getMailSent()]); + } + + /** + * {@inheritdoc} + */ + public function getIcon() { + return 'iVBORw0KGgoAAAANSUhEUgAAABcAAAAcCAYAAACK7SRjAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYwIDYxLjEzNDc3NywgMjAxMC8wMi8xMi0xNzozMjowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNSBNYWNpbnRvc2giIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6N0NEOTU1MjM0OThFMTFFMDg3NzJBNTE2ODgwQzMxMzQiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6N0NEOTU1MjQ0OThFMTFFMDg3NzJBNTE2ODgwQzMxMzQiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDoxMEQ5OTQ5QzQ5OEMxMUUwODc3MkE1MTY4ODBDMzEzNCIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo3Q0Q5NTUyMjQ5OEUxMUUwODc3MkE1MTY4ODBDMzEzNCIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PpkRnSAAAAJ0SURBVHjaYvz//z8DrQATAw3BqOFYAaO9vT1FBhw4cGCAXA5MipxBQUHT3r17l0AVAxkZ/wkLC89as2ZNIcjlYkALXKnlWqBZTH/+/PEDmQsynLW/v3+NoaHhN2oYDjJn8uTJK4BMNpDhPwsLCwOKiop2+fn5vafEYC8vrw8gc/Lz8wOB3B8gw/nev38vn5WV5eTg4LA/Ly/vESsrK2npmYmJITU19SnQ8L0gc4DxpwgyF2S4EEjB58+f+crLy31YWFjOt7S0XBYUFPxHjMEcHBz/6+rqboqJiZ0qKSnxBpkDlRICGc4MU/j792+2CRMm+L18+fLSxIkTDykoKPzBZ7CoqOi/np6eE8rKylvb29v9fvz4wYEkzYKRzjk5OX/LyMjcnDRpEkjjdisrK6wRraOj8wvokAMLFy788ejRoxcaGhrPCWai4ODgB8DUE3/mzBknYMToASNoMzAfvEVW4+Tk9LmhoWFbTU2NwunTpx2BjiiMjo6+hm4WCzJHUlLyz+vXrxkfP36sDOI/ffpUPikpibe7u3sLsJjQW7VqlSrQxe+Avjmanp7u9PbtWzGQOmCCkARmmu/m5uYfT548yY/V5UpKSl+2b9+uiiz26dMnIWBSDTp27NgdYGrYCIzwE7m5uR4wg2Hg/PnzSsDI/QlKOcjZ3wGUBLm5uf+DwLdv38gub4AG/xcSEvr35s0bZmCB5sgCE/z69SsjyDJKMtG/f/8YQQYD8wkoGf8H51AbG5sH1CpbQBnQ09PzBiiHgoIFFHlBQGwLxLxUMP8dqJgH4k3gIhfIkAKVYkDMTmmhCHIxEL8A4peMo02L4WU4QIABANxZAoDIQDv3AAAAAElFTkSuQmCC'; + } + +} diff --git a/web/modules/contrib/devel/webprofiler/src/DataCollector/PerformanceTimingDataCollector.php b/web/modules/contrib/devel/webprofiler/src/DataCollector/PerformanceTimingDataCollector.php new file mode 100644 index 000000000..e0e207caa --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/src/DataCollector/PerformanceTimingDataCollector.php @@ -0,0 +1,84 @@ +data['performance'] = $data; + } + + /** + * {@inheritdoc} + */ + public function getName() { + return 'performance_timing'; + } + + /** + * {@inheritdoc} + */ + public function getTitle() { + return $this->t('Performance Timing'); + } + + /** + * {@inheritdoc} + */ + public function getPanelSummary() { + if (isset($this->data['performance'])) { + $performanceData = new PerformanceTimingData($this->data['performance']); + return $this->t('TTFB: @ttfb', ['@ttfb' => sprintf('%.0f ms', $performanceData->getTtfbTiming())]); + } + + return NULL; + } + + /** + * {@inheritdoc} + */ + public function getIcon() { + return 'iVBORw0KGgoAAAANSUhEUgAAABUAAAAcCAYAAACOGPReAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAQJJREFUeNpi/P//PwO1ARMDDcCooUPEUEYi1AgAcQIQ+0P5H4B4IxAvwKaYmCSqAMT7gbgBajhMrB8qLkCsoQFQQ0CuOw/EBjgsLIAajmEouvdBhukD8UQgdkASwwXuA7EhNEhwuvQ8iXHSj2Q53FBY7BtADVxIoqEfoQYnYJPEF3bEROZ6WDDBvO+ALcCxJCsBAmpA4SuA7P2PBDQUEOGTDTA1TNCYs6dCRgIlxQswQ0GMB0A8nwgv4gqa+VCXgpMWC1QiEerF9WgaDmJJp/OhkUNIHUHQgJ4ecQHkiMKXXALQIowqpdR8pJi/AA0qvC4lFsyHYqK8zzhaRQ8NQwECDABNaU12xhTp2QAAAABJRU5ErkJggg=='; + } + + /** + * {@inheritdoc} + */ + public function getData() { + $data = $this->data; + + if (isset($this->data['performance'])) { + $performanceData = new PerformanceTimingData($this->data['performance']); + $data['performance']['computed']['DNS lookup time'] = $performanceData->getDNSTiming(); + $data['performance']['computed']['TCP handshake time'] = $performanceData->getTCPTiming(); + $data['performance']['computed']['Time to first byte'] = $performanceData->getTtfbTiming(); + $data['performance']['computed']['Data download time'] = $performanceData->getDataTiming(); + $data['performance']['computed']['DOM building time'] = $performanceData->getDomTiming(); + } + + return $data; + } + +} diff --git a/web/modules/contrib/devel/webprofiler/src/DataCollector/PhpConfigDataCollector.php b/web/modules/contrib/devel/webprofiler/src/DataCollector/PhpConfigDataCollector.php new file mode 100644 index 000000000..461c8e9b6 --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/src/DataCollector/PhpConfigDataCollector.php @@ -0,0 +1,163 @@ +data = [ + 'token' => $response->headers->get('X-Debug-Token'), + 'php_version' => PHP_VERSION, + 'xdebug_enabled' => extension_loaded('xdebug'), + 'xhprof_enabled' => extension_loaded('xhprof'), + 'eaccel_enabled' => extension_loaded('eaccelerator') && ini_get('eaccelerator.enable'), + 'apc_enabled' => extension_loaded('apc') && ini_get('apc.enabled'), + 'xcache_enabled' => extension_loaded('xcache') && ini_get('xcache.cacher'), + 'wincache_enabled' => extension_loaded('wincache') && ini_get('wincache.ocenabled'), + 'zend_opcache_enabled' => extension_loaded('Zend OPcache') && ini_get('opcache.enable'), + 'sapi_name' => php_sapi_name(), + ]; + } + + /** + * Gets the token. + * + * @return string The token + */ + public function getToken() { + return $this->data['token']; + } + + /** + * Gets the PHP version. + * + * @return string The PHP version + */ + public function getPhpVersion() { + return $this->data['php_version']; + } + + /** + * Returns true if the XDebug is enabled. + * + * @return Boolean true if XDebug is enabled, false otherwise + */ + public function hasXDebug() { + return $this->data['xdebug_enabled']; + } + + /** + * Returns true if the XHProf is enabled. + * + * @return Boolean true if XHProf is enabled, false otherwise + */ + public function hasXHProf() { + return $this->data['xhprof_enabled']; + } + + /** + * Returns true if EAccelerator is enabled. + * + * @return Boolean true if EAccelerator is enabled, false otherwise + */ + public function hasEAccelerator() { + return $this->data['eaccel_enabled']; + } + + /** + * Returns true if APC is enabled. + * + * @return Boolean true if APC is enabled, false otherwise + */ + public function hasApc() { + return $this->data['apc_enabled']; + } + + /** + * Returns true if Zend OPcache is enabled. + * + * @return Boolean true if Zend OPcache is enabled, false otherwise + */ + public function hasZendOpcache() { + return $this->data['zend_opcache_enabled']; + } + + /** + * Returns true if XCache is enabled. + * + * @return Boolean true if XCache is enabled, false otherwise + */ + public function hasXCache() { + return $this->data['xcache_enabled']; + } + + /** + * Returns true if WinCache is enabled. + * + * @return Boolean true if WinCache is enabled, false otherwise + */ + public function hasWinCache() { + return $this->data['wincache_enabled']; + } + + /** + * Returns true if any accelerator is enabled. + * + * @return Boolean true if any accelerator is enabled, false otherwise + */ + public function hasAccelerator() { + return $this->hasApc() || $this->hasZendOpcache() || $this->hasEAccelerator() || $this->hasXCache() || $this->hasWinCache(); + } + + /** + * Gets the PHP SAPI name. + * + * @return string The environment + */ + public function getSapiName() { + return $this->data['sapi_name']; + } + + /** + * {@inheritdoc} + */ + public function getTitle() { + return $this->t('PHP Config'); + } + + /** + * {@inheritdoc} + */ + public function getPanelSummary() { + return $this->t('PHP: @version', ['@version' => $this->getPhpVersion()]); + } + + /** + * {@inheritdoc} + */ + public function getName() { + return 'php_config'; + } + + /** + * {@inheritdoc} + */ + public function getIcon() { + return 'iVBORw0KGgoAAAANSUhEUgAAABUAAAAcCAMAAAC5xgRsAAAAZlBMVEX///////////////////////////////////////////////////////////////////////////////////////////+ZmZmZmZlISEhJSUmdnZ1HR0fR0dFZWVlpaWlfX18/Pz+puygPAAAAIXRSTlMACwwlJygpLzIzNjs8QEtMUmd6e32AucDBw8fIydTm6u5l8MjvAAAAo0lEQVR42r2P2Q6CMBBFL6XsZRGRfZv//0nbDBNEE19MnJeTc5ILKf58ahiUwzy/AJpIWwREwQnEXRdbGCLjrO+djWRvVMiJcigxB7viGogxDdJpSmHEmCVPS7YczJvgUu+CS30IvtbNYZMvsGVo2mVpG/kbm4auiCpdcC3YPCAhSpAdUzaAn6qPKZtUT6ZSzb4bi2hdo9MQ1nX4ASjfV+/4/Z40pyCHrNTbIgAAAABJRU5ErkJggg=='; + } + +} diff --git a/web/modules/contrib/devel/webprofiler/src/DataCollector/RequestDataCollector.php b/web/modules/contrib/devel/webprofiler/src/DataCollector/RequestDataCollector.php new file mode 100644 index 000000000..57b2e6d32 --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/src/DataCollector/RequestDataCollector.php @@ -0,0 +1,112 @@ +controllerResolver = $controllerResolver; + $this->accessCheck = []; + } + + /** + * {@inheritdoc} + */ + public function collect(Request $request, Response $response, \Exception $exception = NULL) { + parent::collect($request, $response, $exception); + + if ($controller = $this->controllerResolver->getController($request)) { + $this->data['controller'] = $this->getMethodData($controller[0], $controller[1]); + $this->data['access_check'] = $this->accessCheck; + } + } + + /** + * @param $service_id + * @param $callable + * @param \Symfony\Component\HttpFoundation\Request $request + */ + public function addAccessCheck($service_id, $callable, Request $request) { + $this->accessCheck[$request->getPathInfo()][] = [ + 'service_id' => $service_id, + 'callable' => $this->getMethodData($callable[0], $callable[1]), + ]; + } + + /** + * {@inheritdoc} + */ + public function getTitle() { + return $this->t('Request'); + } + + /** + * {@inheritdoc} + */ + public function getPanelSummary() { + return $this->data['status_code'] . ' ' . $this->data['status_text']; + } + + /** + * {@inheritdoc} + */ + public function getIcon() { + return 'iVBORw0KGgoAAAANSUhEUgAAABwAAAAcCAQAAADYBBcfAAACvElEQVR42tVTbUhTYRTerDCnKVoUUr/KCZmypA9Koet0bXNLJ5XazDJ/WFaCUY0pExRZXxYiJgsxWWjkaL+yK+po1gjyR2QfmqWxtBmaBtqWGnabT++c11Fu4l/P4VzOPc95zoHznsNZodIbLDdRcKnc1Bu8DAK45ZsOnykQNMopsNooLxCknb0cDq5vml9FtHiIgpBR0R6iihYyFMTDt2Lg56ObPkI6TMGXSof1EV67IqCwisJSWliFAG/E0CfFIiebdNypcxi/1zgyFiIiZ3sJQr0RQx5frLa6k7SOKRo3oMFNR5t62h2rttKXEOKFqDCxtXNmmBokO2KKTlp3IdWuT2dYRNGKwEXEBCcL172G5FG0aIxC0kR9PBTVH1kkwQn+IqJnCE33EalVzT9GJQS1tAdD3CKicJYFrxqx7W2ejCEdZy1FiC5tZxHhLJKOZaRdQJAyV/YAvDliySALHxmxR4Hqe2iwvaOR/CEuZYJFSgYhVbZRkA8KGdEktrqnqra90NndCdkt77fjIHIhexOrfO6O3bbbOj/rqu5IptgyR3sU93QbOYhquZK4MCDp0Ina/PLsu5JvbCTRaapUdUmIV/RzoMdsk/0hWRNdAvKOmvqlN0drsJbJf1P4YsQ5lGrJeuosiOUgbOC8cto3LfOXTdVd7BqZsQKbse+0jUL6WPcesqs4MNSUTQAxGjwFiC8m3yzmqwHJBWYKBJ9WNqW/dHkpU/osch1Yj5RJfXPfSEe/2UPsN490NPfZG5CKyJmcV5ayHyzy7BMqsXfuHhGK/cjAIeSpR92gehR55D8TcQhDEKJwytBJ4fr4NULvrEM8NszfJPyxDoHYAQ1oPCWmIX4gifmDS/DV2DKeb25FHWr76yEG7/9L4YFPeiQQ4/8LkgJ8Et+NncTCsYqzXAEXa7CWdPZzGWdlyV+vST0JanfPvwAAAABJRU5ErkJggg=='; + } + + /** + * @return array|string + */ + public function getData() { + // Drupal 8.5+ uses Symfony 3.4.x that changes the way the Request data are + // collected. Data is altered with \Symfony\Component\HttpKernel\DataCollector\DataCollector::cloneVar. + // The stored data (of type \Symfony\Component\VarDumper\Cloner\Data) is + // suitable to be converted to a string by a Dumper (\Symfony\Component\VarDumper\Dumper\DataDumperInterface). + // In our implementation however we need that data as an array, to be later + // converted in a json response by a REST endpoint. We need to refactor the + // whole way Web Profiler works to allow that. At the moment we just + // retrieve the raw Data value and do some string manipulation to clean the + // output a bit. + $data = $this->data->getValue(TRUE); + unset($data['request_attributes']['_route_params']); + unset($data['request_attributes']['_access_result']); + + $route_object = []; + foreach ($data['request_attributes']['_route_object'] as $key => $result) { + $key = str_replace("\0", '', $key); + $key = str_replace('Symfony\Component\Routing\Route', 'Symfony\Component\Routing\Route::', $key); + $route_object[$key] = $result; + } + $data['request_attributes']['_route_object'] = $route_object; + + return $data; + } + +} diff --git a/web/modules/contrib/devel/webprofiler/src/DataCollector/RoutingDataCollector.php b/web/modules/contrib/devel/webprofiler/src/DataCollector/RoutingDataCollector.php new file mode 100644 index 000000000..e9495c4f2 --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/src/DataCollector/RoutingDataCollector.php @@ -0,0 +1,92 @@ +routeProvider = $routeProvider; + } + + /** + * {@inheritdoc} + */ + public function collect(Request $request, Response $response, \Exception $exception = NULL) { + $this->data['routing'] = []; + foreach ($this->routeProvider->getAllRoutes() as $route_name => $route) { + // @TODO Find a better visual representation. + $this->data['routing'][] = [ + 'name' => $route_name, + 'path' => $route->getPath(), + ]; + } + } + + /** + * @return int + */ + public function getRoutesCount() { + return count($this->routing()); + } + + /** + * Twig callback for displaying the routes. + */ + public function routing() { + return $this->data['routing']; + } + + /** + * {@inheritdoc} + */ + public function getTitle() { + return $this->t('Routing'); + } + + /** + * {@inheritdoc} + */ + public function getName() { + return 'routing'; + } + + /** + * {@inheritdoc} + */ + public function getPanelSummary() { + return $this->t('Defined routes: @route', ['@route' => $this->getRoutesCount()]); + } + + /** + * {@inheritdoc} + */ + public function getIcon() { + return 'iVBORw0KGgoAAAANSUhEUgAAABUAAAAcCAYAAACOGPReAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAPxJREFUeNrkVsERREAQ7D0koXwlIAISkIGXvwAkoJSfHITg5SMUVR4eMuCxZ1xxV+5uuTL30lV8bPX09PSuFVJKcOOGP+DipLrrusoFdV1Lz/NgmiaKosC0XrAopYT0fc/f/jAMa41Pj23bkrpi9bRpGmRZ9lRKFSzLkl9UHMI4jijLcuaaSZMkQdd1fNMn5r0EHIFhGEjTlDenYRjCcZyHUnqRwXEcz77sYepM+Z1yTOEXZEFVVYcUba2iItsNcbr9IAiw5HMd1CJZtU1VpG3bIooi3gPF933keY43pb9gb1Bskdrap58luMjvRNO04wcK18RfIa59mbgLMAASuWsKAyoEhgAAAABJRU5ErkJggg=='; + } + +} diff --git a/web/modules/contrib/devel/webprofiler/src/DataCollector/ServicesDataCollector.php b/web/modules/contrib/devel/webprofiler/src/DataCollector/ServicesDataCollector.php new file mode 100644 index 000000000..fc885fc8d --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/src/DataCollector/ServicesDataCollector.php @@ -0,0 +1,163 @@ +container = $container; + } + + /** + * {@inheritdoc} + */ + public function collect(Request $request, Response $response, \Exception $exception = NULL) { + if ($this->getServicesCount()) { + + $tracedData = []; + if ($this->container instanceof TraceableContainer) { + $tracedData = $this->container->getTracedData(); + } + + foreach (array_keys($this->getServices()) as $id) { + $this->data['services'][$id]['initialized'] = ($this->container->initialized($id)) ? TRUE : FALSE; + $this->data['services'][$id]['time'] = isset($tracedData[$id]) ? $tracedData[$id] : NULL; + } + } + } + + /** + * @param $services + */ + public function setServices($services) { + $this->data['services'] = $services; + } + + /** + * @return array + */ + public function getServices() { + return $this->data['services']; + } + + /** + * @return int + */ + public function getServicesCount() { + return count($this->getServices()); + } + + /** + * @return array + */ + public function getInitializedServices() { + return array_filter($this->getServices(), function ($item) { + return $item['initialized']; + }); + } + + /** + * @return int + */ + public function getInitializedServicesCount() { + return count($this->getInitializedServices()); + } + + /** + * @return array + */ + public function getInitializedServicesWithoutWebprofiler() { + return array_filter($this->getInitializedServices(), function ($item) { + return strpos($item['value']['id'], 'webprofiler') !== 0; + }); + } + + /** + * @return int + */ + public function getInitializedServicesWithoutWebprofilerCount() { + return count($this->getInitializedServicesWithoutWebprofiler()); + } + + /** + * {@inheritdoc} + */ + public function getName() { + return 'services'; + } + + /** + * {@inheritdoc} + */ + public function getTitle() { + return $this->t('Services'); + } + + /** + * {@inheritdoc} + */ + public function getPanelSummary() { + return $this->t('Initialized: @count', [ + '@count' => $this->getInitializedServicesCount(), + ]); + } + + /** + * {@inheritdoc} + */ + public function getIcon() { + return 'iVBORw0KGgoAAAANSUhEUgAAABUAAAAcCAYAAACOGPReAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAQVJREFUeNrkVe0NgjAQBeMAdYO6AWxQNtANGEFHcALZANyADegGsIFsIBvgu6Q/LtWmxdTEjyYvd6Hw8t5de6TzPCex1yp5w/pz0rVrQymVIXSAACqt9TGG0p0hpHWIZb9lebWENOXn1FgWbL8GJHACNHs+ohyjlxSEZPEcKGYC6SbEvljgUHzEOR3IXiiB6YOTlLqdo1Y54tZHDLIauCHtETtn962P6EUVqhhi0gelIJEEk1MjMg9Py9xol/0SuBqFva/DULY3ZSqQF767v8TyZKv83tFXWVaEufsUG+DCr2nwQLGOlGQNizZPy3fMU16K5uV5+qQEpFTC+hCN9Pd/0XcBBgBxwVqjDkAznAAAAABJRU5ErkJggg=='; + } + + /** + * @return array + */ + public function getData() { + $data = $this->data; + + $http_middleware = array_filter($data['services'], function ($service) { + return isset($service['value']['tags']['http_middleware']); + }); + + foreach ($http_middleware as &$service) { + $service['value']['handle_method'] = $this->getMethodData($service['value']['class'], 'handle'); + } + + uasort($http_middleware, function ($a, $b) { + $va = $a['value']['tags']['http_middleware'][0]['priority']; + $vb = $b['value']['tags']['http_middleware'][0]['priority']; + + if ($va == $vb) { + return 0; + } + return ($va > $vb) ? -1 : 1; + }); + + $data['http_middleware'] = $http_middleware; + + return $data; + } + +} diff --git a/web/modules/contrib/devel/webprofiler/src/DataCollector/StateDataCollector.php b/web/modules/contrib/devel/webprofiler/src/DataCollector/StateDataCollector.php new file mode 100644 index 000000000..19f057fcb --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/src/DataCollector/StateDataCollector.php @@ -0,0 +1,66 @@ +data['state_get'][$key] = isset($this->data['state_get'][$key]) ? $this->data['state_get'][$key] + 1 : 1; + } + + /** + * Twig callback to show all requested state keys. + */ + public function getStateKeysCount() { + return count($this->data['state_get']); + } + + /** + * {@inheritdoc} + */ + public function getTitle() { + return $this->t('State'); + } + + /** + * {@inheritdoc} + */ + public function getName() { + return 'state'; + } + + /** + * {@inheritdoc} + */ + public function getPanelSummary() { + return $this->t('Total: @variables', ['@variables' => $this->getStateKeysCount()]); + } + + /** + * {@inheritdoc} + */ + public function getIcon() { + return 'iVBORw0KGgoAAAANSUhEUgAAABUAAAAcCAYAAACOGPReAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyRpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYxIDY0LjE0MDk0OSwgMjAxMC8xMi8wNy0xMDo1NzowMSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNS4xIE1hY2ludG9zaCIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDo2Njc3QTVERTkxNkMxMUUzQjA3OUEzQTNEMUVGMjVDOCIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDo2Njc3QTVERjkxNkMxMUUzQjA3OUEzQTNEMUVGMjVDOCI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOjY2NzdBNURDOTE2QzExRTNCMDc5QTNBM0QxRUYyNUM4IiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOjY2NzdBNUREOTE2QzExRTNCMDc5QTNBM0QxRUYyNUM4Ii8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+tji6wwAAAUFJREFUeNpi/P//PwO1ARMDDcDQMZSBgZExE4h/AvF/KuA/QFzKCCS+TZ40iVNeXp5i9718+ZIhNS3tL5OoqChVDAQBcXFxBqBZzDQJU0Z2Do7/FubmVDPwzNmzwymdhoSEgDEM8PDwgPkJCQk49bDgMxCkEWQAyKAXL14wXLhwgWHChAlguTVr1pBnKMiwgoICsEEgC+7cuQPGHR0deH3HzMLC0iAjI4NV8tSpUwzv3r1jYGNjY3BycoJbgg88e/6cuIg6cuQImAZ5n2qx7+HhAaZtbGzArqXYUJCBIAwKR1jMq6ioMBgYGJAeURUVFWCNsHAERRAsKYFofJGF01CQIaBktGPHDjANAjU1NWBXg8IWX/gOobz/8+fP7x8/fqSKYV++fGH4+vXrX1B1EgXET6hUnTwH4jTG0Xqf6gAgwABnDJn0eW/JMwAAAABJRU5ErkJggg=='; + } + +} diff --git a/web/modules/contrib/devel/webprofiler/src/DataCollector/ThemeDataCollector.php b/web/modules/contrib/devel/webprofiler/src/DataCollector/ThemeDataCollector.php new file mode 100644 index 000000000..5fb38c812 --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/src/DataCollector/ThemeDataCollector.php @@ -0,0 +1,258 @@ +themeManager = $themeManager; + $this->themeNegotiator = $themeNegotiator; + $this->profile = $profile; + } + + /** + * {@inheritdoc} + */ + public function collect(Request $request, Response $response, \Exception $exception = NULL) { + $activeTheme = $this->themeManager->getActiveTheme(); + + $this->data['activeTheme'] = [ + 'name' => $activeTheme->getName(), + 'path' => $activeTheme->getPath(), + 'engine' => $activeTheme->getEngine(), + 'owner' => $activeTheme->getOwner(), + 'baseThemes' => $activeTheme->getBaseThemeExtensions(), + 'extension' => $activeTheme->getExtension(), + 'styleSheetsRemove' => $activeTheme->getStyleSheetsRemove(), + 'libraries' => $activeTheme->getLibraries(), + 'regions' => $activeTheme->getRegions(), + ]; + + if ($this->themeNegotiator instanceof ThemeNegotiatorWrapper) { + $this->data['negotiator'] = [ + 'class' => $this->getMethodData($this->themeNegotiator->getNegotiator(), 'determineActiveTheme'), + 'id' => $this->themeNegotiator->getNegotiator()->_serviceId, + ]; + } + } + + /** + * {@inheritdoc} + */ + public function lateCollect() { + $this->data['twig'] = serialize($this->profile); + } + + /** + * @return string + */ + public function getActiveTheme() { + return $this->data['activeTheme']; + } + + /** + * @return array + */ + public function getThemeNegotiator() { + return $this->data['negotiator']; + } + + /** + * @return int + */ + public function getTime() { + return $this->getProfile()->getDuration() * 1000; + } + + /** + * @return mixed + */ + public function getTemplateCount() { + return $this->getComputedData('template_count'); + } + + /** + * @return mixed + */ + public function getTemplates() { + return $this->getComputedData('templates'); + } + + /** + * @return mixed + */ + public function getBlockCount() { + return $this->getComputedData('block_count'); + } + + /** + * @return mixed + */ + public function getMacroCount() { + return $this->getComputedData('macro_count'); + } + + /** + * @return \Twig_Markup + */ + public function getHtmlCallGraph() { + $dumper = new HtmlDumper(); + + return new \Twig_Markup($dumper->dump($this->getProfile()), 'UTF-8'); + } + + /** + * {@inheritdoc} + */ + public function getName() { + return 'theme'; + } + + /** + * {@inheritdoc} + */ + public function getTitle() { + return $this->t('Theme'); + } + + /** + * {@inheritdoc} + */ + public function getPanelSummary() { + return $this->t('Name: @name', ['@name' => $this->getActiveTheme()['name']]); + } + + /** + * {@inheritdoc} + */ + public function getIcon() { + return 'iVBORw0KGgoAAAANSUhEUgAAABUAAAAcCAYAAACOGPReAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAlVJREFUeNrMlk1IVFEUx53K6AOjUBQri6SihQiRziaRQegDlKJFBCpSiZtoE4hWtFFRwcCVBqEWfaAE7SoXajXLQoRcFAUSaBgUJQxKYUrT78D/wevx3psaE7rw49x377ln7vl4500kmUxm/OuxJmMVxrqwzVgsth5xHKogCnthCd7BU7gdj8envOciQe5j8CyiFQq0tAgDsA+OaG0eLmF4INQoxrYg+iEXhqEGiqGBw/3SGUeU6MgC61mBMUU5GzEKJ6ES5S5knbbXui/jmv8MjCkGMxEPFbsFyAeL1y6pXEenSO4fctlIhCXqqtmGj5ADcblZoX1z8aLn/Es475soDhcyfwt3LPDK8jPYFlIcZrCCEH0LqlP7NXN/CCUL/CvmkynKcczPoNv9Y5LXuHVC2Y6mMFoetOG4/0kl9LfDquMyN076ue/I11ANHSqVH0pgLUz7GK2HjUHuf1DGm/jVYZVYJeILz5163oy46Tn/GL5r396yc+hXOzcckiyVQp7CsVNvmI2DHoODcMFcR6eZ+RO4747pJuYjcFilssMM6vB7+Or8IGMWWjDWx7ntzHvglCWZtfbf3n0UtiJ6FVO/8QbuQh+H59C37nUD9sAj1k6ENZQylctuJWsGJswDDibY32C3gitKsCUwyt7nlK0vpMc2Wh/Q4zIcxeDztDu/QjSoPmujzWswZef3GLRXuV2xPa3u/yDwjfoDgxE1nP1aquOG91b64StWlp0xtaKvKbfMUS1maukWvEj7a6pxBg6oL1iddnsbSFox/S/+TKyK0V8CDABrCdI/1oTqiQAAAABJRU5ErkJggg=='; + } + + /** + * @return array + */ + public function getData() { + $data = $this->data; + + $data['twig'] = [ + 'callgraph' => (string) $this->getHtmlCallGraph(), + 'render_time' => $this->getTime(), + 'template_count' => $this->getTemplateCount(), + 'templates' => $this->getTemplates(), + 'block_count' => $this->getBlockCount(), + 'macro_count' => $this->getMacroCount(), + ]; + + return $data; + } + + /** + * @return mixed|\Twig_Profiler_Profile + */ + private function getProfile() { + if (NULL === $this->profile) { + $this->profile = unserialize($this->data['twig']); + } + + return $this->profile; + } + + /** + * @param $index + * + * @return mixed + */ + private function getComputedData($index) { + if (NULL === $this->computed) { + $this->computed = $this->computeData($this->getProfile()); + } + + return $this->computed[$index]; + } + + /** + * @param \Twig_Profiler_Profile $profile + * + * @return array + */ + private function computeData(\Twig_Profiler_Profile $profile) { + $data = [ + 'template_count' => 0, + 'block_count' => 0, + 'macro_count' => 0, + ]; + + $templates = []; + foreach ($profile as $p) { + $d = $this->computeData($p); + + $data['template_count'] += ($p->isTemplate() ? 1 : 0) + $d['template_count']; + $data['block_count'] += ($p->isBlock() ? 1 : 0) + $d['block_count']; + $data['macro_count'] += ($p->isMacro() ? 1 : 0) + $d['macro_count']; + + if ($p->isTemplate()) { + if (!isset($templates[$p->getTemplate()])) { + $templates[$p->getTemplate()] = 1; + } + else { + $templates[$p->getTemplate()]++; + } + } + + foreach ($d['templates'] as $template => $count) { + if (!isset($templates[$template])) { + $templates[$template] = $count; + } + else { + $templates[$template] += $count; + } + } + } + $data['templates'] = $templates; + + return $data; + } + +} diff --git a/web/modules/contrib/devel/webprofiler/src/DataCollector/TimeDataCollector.php b/web/modules/contrib/devel/webprofiler/src/DataCollector/TimeDataCollector.php new file mode 100644 index 000000000..0a19fb167 --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/src/DataCollector/TimeDataCollector.php @@ -0,0 +1,157 @@ +data['memory_limit'] = $this->convertToBytes(ini_get('memory_limit')); + $this->updateMemoryUsage(); + } + + /** + * {@inheritdoc} + */ + public function lateCollect() { + parent::lateCollect(); + + $this->updateMemoryUsage(); + } + + /** + * Gets the memory. + * + * @return int + * The memory + */ + public function getMemory() { + return $this->data['memory']; + } + + /** + * Gets the PHP memory limit. + * + * @return int + * The memory limit + */ + public function getMemoryLimit() { + return $this->data['memory_limit']; + } + + /** + * Updates the memory usage data. + */ + public function updateMemoryUsage() { + $this->data['memory'] = memory_get_peak_usage(TRUE); + } + + /** + * {@inheritdoc} + */ + public function getTitle() { + return $this->t('Timeline'); + } + + /** + * {@inheritdoc} + */ + public function getPanelSummary() { + return $this->t('Duration: @duration', ['@duration' => sprintf('%.0f ms', $this->getDuration())]); + } + + /** + * {@inheritdoc} + */ + public function getIcon() { + return 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAcCAYAAABoMT8aAAABqUlEQVR42t2Vv0sCYRyHX9OmEhsMx/YKGlwLQ69DTEUSBJEQEy5J3FRc/BsuiFqEIIcQIRo6ysUhoaBBWhoaGoJwiMJLglRKrs8bXgienmkQdPDAwX2f57j3fhFJkkbiPwTK5bIiFoul3kmPud8MqKMewDXpwuGww+12n9hsNhFnlijYf/Z4PDmO45Yxo+10ZFGTyWRMEItU6AdCx7lczkgd6n7J2Wx2xm63P6jJMk6n80YQBBN1aUDv9XqvlAbbm2LE7/cLODRB0un0VveAeoDC8/waCQQC18MGQqHQOcEKvw8bcLlcL6TfYnVtCrGRAlartUUYhmn1jKg/E3USjUYfhw3E4/F7ks/nz4YNFIvFQ/ogbUYikdefyqlU6gnuOg2YK5XKvs/n+xhUDgaDTVEUt+HO04ABOBA5isViDTU5kUi81Wq1AzhWMEkDGmAEq2C3UCjcYXGauDvfEsuyUjKZbJRKpVvM8IABU9SVX+cxYABmwIE9cFqtVi9xtgvsC2AHbIAFoKey0gdlHEyDObAEWLACFsEsMALdIJ80+dK0bTS95v7+v/AJnis0eO906QwAAAAASUVORK5CYII='; + } + + /** + * {@inheritdoc} + */ + public function getLibraries() { + return [ + 'webprofiler/timeline', + ]; + } + + /** + * {@inheritdoc} + */ + public function getDrupalSettings() { + /** @var \Symfony\Component\Stopwatch\StopwatchEvent[] $collectedEvents */ + $collectedEvents = $this->getEvents(); + + if (!empty($collectedEvents)) { + $sectionPeriods = $collectedEvents['__section__']->getPeriods(); + $endTime = end($sectionPeriods)->getEndTime(); + $events = []; + + foreach ($collectedEvents as $key => $collectedEvent) { + if ('__section__' != $key) { + $periods = []; + foreach ($collectedEvent->getPeriods() as $period) { + $periods[] = [ + 'start' => sprintf("%F", $period->getStartTime()), + 'end' => sprintf("%F", $period->getEndTime()), + ]; + } + + $events[] = [ + "name" => $key, + "category" => $collectedEvent->getCategory(), + "origin" => sprintf("%F", $collectedEvent->getOrigin()), + "starttime" => sprintf("%F", $collectedEvent->getStartTime()), + "endtime" => sprintf("%F", $collectedEvent->getEndTime()), + "duration" => sprintf("%F", $collectedEvent->getDuration()), + "memory" => sprintf("%.1F", $collectedEvent->getMemory() / 1024 / 1024), + "periods" => $periods, + ]; + } + } + + return ['time' => ['events' => $events, 'endtime' => $endTime]]; + } + else { + return ['time' => ['events' => [], 'endtime' => 0]]; + } + } + + /** + * @return array + */ + public function getData() { + $data = $this->data; + + $data['duration'] = $this->getDuration(); + $data['initTime'] = $this->getInitTime(); + + return $data; + } + +} diff --git a/web/modules/contrib/devel/webprofiler/src/DataCollector/TranslationsDataCollector.php b/web/modules/contrib/devel/webprofiler/src/DataCollector/TranslationsDataCollector.php new file mode 100644 index 000000000..a3ab1418f --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/src/DataCollector/TranslationsDataCollector.php @@ -0,0 +1,100 @@ +translation = $translation; + $this->urlGenerator = $urlGenerator; + + $this->data['translations']['translated'] = []; + $this->data['translations']['untranslated'] = []; + } + + /** + * {@inheritdoc} + */ + public function collect(Request $request, Response $response, \Exception $exception = NULL) { + if ($this->translation instanceof TranslationManagerWrapper) { + /** \Drupal\webprofiler\StringTranslation\TranslationManagerWrapper $this->translation */ + $this->data['translations']['translated'] = $this->translation->getTranslated(); + $this->data['translations']['untranslated'] = $this->translation->getUntranslated(); + } + $this->data['user_interface_translations_path'] = $this->urlGenerator->generateFromRoute('locale.translate_page'); + } + + /** + * @return int + */ + public function getTranslatedCount() { + return count($this->data['translations']['translated']); + } + + /** + * @return int + */ + public function getUntranslatedCount() { + return count($this->data['translations']['untranslated']); + } + + /** + * {@inheritdoc} + */ + public function getName() { + return 'translations'; + } + + /** + * {@inheritdoc} + */ + public function getTitle() { + return $this->t('Translations'); + } + + /** + * {@inheritdoc} + */ + public function getPanelSummary() { + return $this->t('Translated: @translated, untranslated: @untranslated', [ + '@translated' => $this->getTranslatedCount(), + '@untranslated' => $this->getUntranslatedCount(), + ]); + } + + /** + * {@inheritdoc} + */ + public function getIcon() { + return 'iVBORw0KGgoAAAANSUhEUgAAABUAAAAcCAYAAACOGPReAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAgpJREFUeNrUVrFuwjAQPUcZiAQDQ6WEbgwwMCJVqhjgBxhYGSvEDzBkZYSBH+ADoFJHlg4MdIMKxiLBXKAbUmEm9bPiyAREEgRDTzqZ2Mfzu7tnJ8xxHLq1aXQHuwsos2078p9arVYg0yL3CXcnwCdubKDp3F+5myFin9xYK0xNzQiZm1c3KpVKUb1ep2QyebvuFwoFSqfTlM/nbweay+VoOBxeDar7JwC03W49UO5fs9nsZTwef8qYUqkUjSmAYrGYqKlhGFSpVH45oBFFdkdM0RjUst1uC7Z45ofjWdO0t8Ph8CDjyuUyZTIZ6na7tNvtTmSn+2s5n88FIMwdPzhgUWYBA2A2mxVjIpEQarEsiwaDAS2XSxPH9OI1xVNnbmo0Go28eTDkAOL3YrGg/X4v1tfr9WmjLplsULVaFawbjYbHkjfzqPsr7o9RwJE2HOkifbAGS1ljdL/G/ScsWK/XE+NmsxGMkW6/3xfgWMMIpu9hLgkEN5tNDwClgAoADjAYRszr8m7kD2gGU5uh1hEjmoGUUU/oGLVEXaVhA2yoKYAwRwFx/Ezj8bjHEIYaSgVgDptNp1NiePG5QCfS4qyZXANop9MR7JANWAFA6hWgWEcmqqTYOWBVl0jZf0ViU+hUNk0AyVf0ObYK0+8IslupF8qlkxVWdoipsaCPiaBr7uwr+t98ofwJMADuIP9IDjFbLwAAAABJRU5ErkJggg=='; + } + +} diff --git a/web/modules/contrib/devel/webprofiler/src/DataCollector/UserDataCollector.php b/web/modules/contrib/devel/webprofiler/src/DataCollector/UserDataCollector.php new file mode 100644 index 000000000..20ab8b952 --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/src/DataCollector/UserDataCollector.php @@ -0,0 +1,144 @@ +currentUser = $current_user; + $this->entityTypeManager = $entity_type_manager; + $this->configFactory = $config_factory; + $this->providerCollector = $provider_collector; + } + + /** + * {@inheritdoc} + */ + public function collect(Request $request, Response $response, \Exception $exception = NULL) { + $this->data['name'] = $this->currentUser->getDisplayName(); + $this->data['authenticated'] = $this->currentUser->isAuthenticated(); + + $this->data['roles'] = []; + $storage = $this->entityTypeManager->getStorage('user_role'); + foreach ($this->currentUser->getRoles() as $role) { + $entity = $storage->load($role); + if ($entity) { + $this->data['roles'][] = $entity->label(); + } + } + + foreach ($this->providerCollector->getSortedProviders() as $provider_id => $provider) { + if ($provider->applies($request)) { + $this->data['provider'] = $provider_id; + } + } + + $this->data['anonymous'] = $this->configFactory->get('user.settings') + ->get('anonymous'); + } + + /** + * @return \Drupal\Core\Session\AccountInterface + */ + public function getUserName() { + return $this->data['name']; + } + + /** + * @return bool + */ + public function getAuthenticated() { + return $this->data['authenticated']; + } + + /** + * @return array + */ + public function getRoles() { + return $this->data['roles']; + } + + /** + * @return string + */ + public function getProvider() { + return $this->data['provider']; + } + + /** + * @return string + */ + public function getAnonymous() { + return $this->data['anonymous']; + } + + /** + * {@inheritdoc} + */ + public function getName() { + return 'user'; + } + + /** + * {@inheritdoc} + */ + public function getTitle() { + return $this->t('User'); + } + + /** + * {@inheritdoc} + */ + public function getPanelSummary() { + return $this->getUserName(); + } + + /** + * {@inheritdoc} + */ + public function getIcon() { + return 'iVBORw0KGgoAAAANSUhEUgAAABgAAAAcCAYAAAB75n/uAAAC70lEQVR42u2V3UtTYRzHu+mFwCwK+gO6CEryPlg7yiYx50vDqUwjFIZDSYUk2ZTmCysHvg9ZVggOQZiRScsR4VwXTjEwdKZWk8o6gd5UOt0mbev7g/PAkLONIOkiBx+25/v89vuc85zn2Q5Fo9F95UDwnwhS5HK5TyqVRv8m1JN6k+AiC+fn54cwbgFNIrTQ/J9IqDcJJDGBHsgDgYBSq9W6ysvLPf39/SSUUU7zsQ1yc3MjmN90OBzfRkZG1umzQqGIxPSTkIBjgdDkaGNjoza2kcFgUCE/QvMsq6io2PV6vQu1tbV8Xl7etkql2qqvr/+MbDE/Pz8s9OP2Cjhwwmw29+4R3Kec1WZnZ4fn5uamc3Jyttra2qbH8ero6JgdHh5+CvFHq9X6JZHgzODgoCVW0NPTY0N+ltU2Nzdv4GqXsYSrPp+vDw80aLFYxru6uhyQ/rDb7a8TCVJDodB1jUazTVlxcXGQ5/mbyE+z2u7u7veY38BVT3Z2djopm5qa6isrK/tQWVn5qb29fSGR4DC4PDAwMEsZHuArjGnyGKutq6v7ajQaF6urq9/MzMz0QuSemJiwQDwGkR0POhhXgILjNTU1TaWlpTxlOp1uyWQyaUjMajMzM8Nut/tJQUHBOpZppbCwkM/KytrBznuL9xDVxBMo8KXHYnu6qKjIivmrbIy67x6Px4Yd58W672ApfzY0NCyNjo7OZmRkiAv8fr+O47iwmABXtoXaG3uykF6vX7bZbF6cgZWqqiqezYkKcNtmjO+CF2AyhufgjsvlMiU7vXEF+4C4ALf9CwdrlVAqlcFkTdRqdQSHLUDgBEeSCrArAsiGwENs0XfJBE6ncxm1D8Aj/B6tigkkJSUlmxSwLYhMDeRsyyUCd+lHrWxtbe2aTCbbZTn1ZD92F0Cr8GBfgnsgDZwDt8EzMBmHMXBLqD0PDMAh9Gql3iRIESQSIAXp4CRIBZeEjIvDFZAm1J4C6UK9ROiZcvCn/+8FvwHtDdJEaRY+oQAAAABJRU5ErkJggg=='; + } + +} diff --git a/web/modules/contrib/devel/webprofiler/src/DataCollector/ViewsDataCollector.php b/web/modules/contrib/devel/webprofiler/src/DataCollector/ViewsDataCollector.php new file mode 100644 index 000000000..da509e96f --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/src/DataCollector/ViewsDataCollector.php @@ -0,0 +1,118 @@ +entityTypeManager = $entity_type_manager; + $this->view_executable_factory = $view_executable_factory; + + $this->data['views'] = []; + } + + /** + * {@inheritdoc} + */ + public function collect(Request $request, Response $response, \Exception $exception = NULL) { + $views = $this->view_executable_factory->getViews(); + $storage = $this->entityTypeManager->getStorage('view'); + + /** @var \Drupal\webprofiler\Views\TraceableViewExecutable $view */ + foreach ($views as $view) { + if ($view->executed) { + $data = [ + 'id' => $view->storage->id(), + 'current_display' => $view->current_display, + 'build_time' => $view->getBuildTime(), + 'execute_time' => $view->getExecuteTime(), + 'render_time' => $view->getRenderTime(), + ]; + + $entity = $storage->load($view->storage->id()); + if ($entity->hasLinkTemplate('edit-display-form')) { + $route = $entity->toUrl('edit-display-form'); + $route->setRouteParameter('display_id', $view->current_display); + $data['route'] = $route->toString(); + } + + $this->data['views'][] = $data; + } + } + + // TODO: also use those data. + // $loaded = $this->entityTypeManager->getLoaded('view'); + // + // if ($loaded) { + // /** @var \Drupal\webprofiler\Entity\EntityStorageDecorator $views */ + // foreach ($loaded->getEntities() as $views) { + // $this->data['views'][] = array( + // 'id' => $views->get('id'), + // ); + // } + // }. + } + + /** + * @return int + */ + public function getViewsCount() { + return count($this->data['views']); + } + + /** + * {@inheritdoc} + */ + public function getName() { + return 'views'; + } + + /** + * {@inheritdoc} + */ + public function getTitle() { + return $this->t('Views'); + } + + /** + * {@inheritdoc} + */ + public function getPanelSummary() { + return $this->t('Total: @count', ['@count' => $this->getViewsCount()]); + } + + /** + * {@inheritdoc} + */ + public function getIcon() { + return 'iVBORw0KGgoAAAANSUhEUgAAABUAAAAcCAYAAACOGPReAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA2hpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYxIDY0LjE0MDk0OSwgMjAxMC8xMi8wNy0xMDo1NzowMSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDowNDgwMTE3NDA3MjA2ODExOEY2MkNCNjI0NDY3NzkwRCIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDozNEVFREM2NkQ4MUMxMUUzQkMwRUNBMkQwMzE4QjVBMyIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDozNEVFREM2NUQ4MUMxMUUzQkMwRUNBMkQwMzE4QjVBMyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ1M1LjEgTWFjaW50b3NoIj4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6MDQ4MDExNzQwNzIwNjgxMThGNjJDQjYyNDQ2Nzc5MEQiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6MDQ4MDExNzQwNzIwNjgxMThGNjJDQjYyNDQ2Nzc5MEQiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz6vqYfFAAAAXUlEQVR42mL8//8/A7UBEwMNwKih1AcsIGLz5s1USwK+vr6MLMgcSg2EOW6IhSkycHR0BHth//79jMh8fACmlr4uRbcVnT8apqNhOhqmAxZR1CyoGUfrfaoDgAADAA4QNs9x67RnAAAAAElFTkSuQmCC'; + } + +} diff --git a/web/modules/contrib/devel/webprofiler/src/Decorator.php b/web/modules/contrib/devel/webprofiler/src/Decorator.php new file mode 100644 index 000000000..416b1f1d4 --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/src/Decorator.php @@ -0,0 +1,95 @@ +object = $object; + } + + /** + * Return the original (i.e. non decorated) object. + * + * @return mixed + * The original object. + */ + public function getOriginalObject() { + $object = $this->object; + while ($object instanceof Decorator) { + $object = $object->getOriginalObject(); + } + return $object; + } + + /** + * Returns true if $method is a PHP callable. + * + * @param string $method + * The method name. + * @param bool $checkSelf + * + * @return bool|mixed + */ + public function isCallable($method, $checkSelf = FALSE) { + // Check the original object. + $object = $this->getOriginalObject(); + if (is_callable([$object, $method])) { + return $object; + } + // Check Decorators. + $object = $checkSelf ? $this : $this->object; + while ($object instanceof Decorator) { + if (is_callable([$object, $method])) { + return $object; + } + $object = $this->object; + } + return FALSE; + } + + /** + * @param $method + * @param $args + * + * @return mixed + * + * @throws \Exception + */ + public function __call($method, $args) { + if ($object = $this->isCallable($method)) { + return call_user_func_array([$object, $method], $args); + } + throw new \Exception( + 'Undefined method - ' . get_class($this->getOriginalObject()) . '::' . $method + ); + } + + /** + * @param $property + * + * @return null + */ + public function __get($property) { + $object = $this->getOriginalObject(); + if (property_exists($object, $property)) { + return $object->$property; + } + return NULL; + } + +} diff --git a/web/modules/contrib/devel/webprofiler/src/DependencyInjection/TraceableContainer.php b/web/modules/contrib/devel/webprofiler/src/DependencyInjection/TraceableContainer.php new file mode 100644 index 000000000..b54c26b65 --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/src/DependencyInjection/TraceableContainer.php @@ -0,0 +1,67 @@ +stopwatch && $this->has('stopwatch')) { + $this->stopwatch = parent::get('stopwatch'); + $this->stopwatch->openSection(); + $this->hasStopwatch = TRUE; + } + + if ('stopwatch' === $id) { + return $this->stopwatch; + } + + Timer::start($id); + if ($this->hasStopwatch) { + $e = $this->stopwatch->start($id, 'service'); + } + + $service = parent::get($id, $invalidBehavior); + + $this->tracedData[$id] = Timer::stop($id); + if ($this->hasStopwatch && $e->isStarted()) { + $e->stop(); + } + + return $service; + } + + /** + * @return array + */ + public function getTracedData() { + return $this->tracedData; + } + +} diff --git a/web/modules/contrib/devel/webprofiler/src/DrupalDataCollectorInterface.php b/web/modules/contrib/devel/webprofiler/src/DrupalDataCollectorInterface.php new file mode 100644 index 000000000..84c0014fb --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/src/DrupalDataCollectorInterface.php @@ -0,0 +1,68 @@ +entities = []; + } + + /** + * {@inheritdoc} + */ + public function resetCache(array $ids = NULL) { + $this->getOriginalObject()->resetCache($ids); + } + + /** + * {@inheritdoc} + */ + public function loadMultiple(array $ids = NULL) { + $entities = $this->getOriginalObject()->loadMultiple($ids); + + $this->entities = array_merge($this->entities, $entities); + + return $entities; + } + + /** + * {@inheritdoc} + */ + public function load($id) { + $entity = $this->getOriginalObject()->load($id); + + $this->entities[$id] = $entity; + + return $entity; + } + + /** + * {@inheritdoc} + */ + public function loadUnchanged($id) { + return $this->getOriginalObject()->loadUnchanged($id); + } + + /** + * {@inheritdoc} + */ + public function loadRevision($revision_id) { + return $this->getOriginalObject()->loadRevision($revision_id); + } + + /** + * {@inheritdoc} + */ + public function deleteRevision($revision_id) { + $this->getOriginalObject()->deleteRevision($revision_id); + } + + /** + * {@inheritdoc} + */ + public function loadByProperties(array $values = []) { + $entities = $this->getOriginalObject()->loadByProperties($values); + + $this->entities = array_merge($this->entities, $entities); + + return $entities; + } + + /** + * {@inheritdoc} + */ + public function create(array $values = []) { + return $this->getOriginalObject()->create($values); + } + + /** + * {@inheritdoc} + */ + public function delete(array $entities) { + $this->getOriginalObject()->delete($entities); + } + + /** + * {@inheritdoc} + */ + public function save(EntityInterface $entity) { + return $this->getOriginalObject()->save($entity); + } + + /** + * {@inheritdoc} + */ + public function hasData() { + return $this->getOriginalObject()->hasData(); + } + + /** + * {@inheritdoc} + */ + public function getQuery($conjunction = 'AND') { + return $this->getOriginalObject()->getQuery($conjunction); + } + + /** + * {@inheritdoc} + */ + public function getEntityTypeId() { + return $this->getOriginalObject()->getEntityTypeId(); + } + + /** + * {@inheritdoc} + */ + public function getEntityType() { + return $this->getOriginalObject()->getEntityType(); + } + + /** + * {@inheritdoc} + */ + public static function getIDFromConfigName($config_name, $config_prefix) { + return substr($config_name, strlen($config_prefix . '.')); + } + + /** + * {@inheritdoc} + */ + public function createFromStorageRecord(array $values) { + return $this->getOriginalObject()->createFromStorageRecord($values); + } + + /** + * {@inheritdoc} + */ + public function updateFromStorageRecord(ConfigEntityInterface $entity, array $values) { + return $this->getOriginalObject() + ->updateFromStorageRecord($entity, $values); + } + + /** + * {@inheritdoc} + */ + public function getAggregateQuery($conjunction = 'AND') { + return $this->getOriginalObject()->getAggregateQuery($conjunction); + } + + /** + * {@inheritdoc} + */ + public function loadOverrideFree($id) { + return $this->getOriginalObject()->loadOverrideFree($id); + } + + /** + * {@inheritdoc} + */ + public function loadMultipleOverrideFree(array $ids = NULL) { + return $this->getOriginalObject()->loadMultipleOverrideFree($ids); + } + + /** + * {@inheritdoc} + */ + public function importCreate($name, Config $new_config, Config $old_config) { + $this->getOriginalObject()->importCreate($name, $new_config, $old_config); + } + + /** + * {@inheritdoc} + */ + public function importUpdate($name, Config $new_config, Config $old_config) { + $this->getOriginalObject()->importUpdate($name, $new_config, $old_config); + } + + /** + * {@inheritdoc} + */ + public function importDelete($name, Config $new_config, Config $old_config) { + $this->getOriginalObject()->importDelete($name, $new_config, $old_config); + } + + /** + * {@inheritdoc} + */ + public function importRename($old_name, Config $new_config, Config $old_config) { + $this->getOriginalObject()->importRename($old_name, $new_config, $old_config); + } + + /** + * {@inheritdoc} + */ + public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) { + return new static( + $entity_type, + $container->get('config.factory'), + $container->get('uuid'), + $container->get('language_manager') + ); + } + + /** + * {@inheritdoc} + */ + public function restore(EntityInterface $entity) { + $this->getOriginalObject()->restore($entity); + } + +} diff --git a/web/modules/contrib/devel/webprofiler/src/Entity/Decorators/Config/DomainStorageDecorator.php b/web/modules/contrib/devel/webprofiler/src/Entity/Decorators/Config/DomainStorageDecorator.php new file mode 100644 index 000000000..dec58a2f5 --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/src/Entity/Decorators/Config/DomainStorageDecorator.php @@ -0,0 +1,90 @@ +getOriginalObject()->loadDefaultDomain(); + } + + /** + * {@inheritdoc} + */ + public function loadDefaultId() { + return $this->getOriginalObject()->loadDefaultId(); + } + + /** + * {@inheritdoc} + */ + public function loadMultipleSorted(array $ids = NULL) { + return $this->getOriginalObject()->loadMultipleSorted($ids); + } + + /** + * {@inheritdoc} + */ + public function loadByHostname($hostname) { + return $this->getOriginalObject()->loadByHostname($hostname); + } + + /** + * {@inheritdoc} + */ + public function loadOptionsList() { + return $this->getOriginalObject()->loadOptionsList(); + } + + /** + * {@inheritdoc} + */ + public function sort(DomainInterface $a, DomainInterface $b) { + return $this->getOriginalObject()->sort($a, $b); + } + + /** + * {@inheritdoc} + */ + public function loadSchema() { + return $this->getOriginalObject()->loadSchema(); + } + + /** + * {@inheritdoc} + */ + public function prepareHostname($hostname) { + return $this->getOriginalObject()->prepareHostname($hostname); + } + + /** + * {@inheritdoc} + */ + public function createHostname() { + return $this->getOriginalObject()->createHostname(); + } + + /** + * {@inheritdoc} + */ + public function createMachineName($hostname = NULL) { + return $this->getOriginalObject()->createMachineName($hostname); + } + + /** + * {@inheritdoc} + */ + public function getDefaultScheme() { + return $this->getOriginalObject()->getDefaultScheme(); + } + +} diff --git a/web/modules/contrib/devel/webprofiler/src/Entity/Decorators/Config/ImageStyleStorageDecorator.php b/web/modules/contrib/devel/webprofiler/src/Entity/Decorators/Config/ImageStyleStorageDecorator.php new file mode 100644 index 000000000..ac20ba281 --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/src/Entity/Decorators/Config/ImageStyleStorageDecorator.php @@ -0,0 +1,33 @@ +getOriginalObject()->setReplacementId($name, $replacement); + } + + /** + * {@inheritdoc} + */ + public function getReplacementId($name) { + return $this->getOriginalObject()->getReplacementId($name); + } + + /** + * {@inheritdoc} + */ + public function clearReplacementId($name) { + return $this->getOriginalObject()->clearReplacementId($name); + } + +} diff --git a/web/modules/contrib/devel/webprofiler/src/Entity/Decorators/Config/RoleStorageDecorator.php b/web/modules/contrib/devel/webprofiler/src/Entity/Decorators/Config/RoleStorageDecorator.php new file mode 100644 index 000000000..cfc08f9bc --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/src/Entity/Decorators/Config/RoleStorageDecorator.php @@ -0,0 +1,19 @@ +getOriginalObject()->isPermissionInRoles($permission, $rids); + } + +} diff --git a/web/modules/contrib/devel/webprofiler/src/Entity/Decorators/Config/ShortcutSetStorageDecorator.php b/web/modules/contrib/devel/webprofiler/src/Entity/Decorators/Config/ShortcutSetStorageDecorator.php new file mode 100644 index 000000000..85378c0d4 --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/src/Entity/Decorators/Config/ShortcutSetStorageDecorator.php @@ -0,0 +1,56 @@ +getOriginalObject()->assignUser($shortcut_set, $account); + } + + /** + * {@inheritdoc} + */ + public function unassignUser($account) { + return $this->getOriginalObject()->unassignUser($account); + } + + /** + * {@inheritdoc} + */ + public function deleteAssignedShortcutSets(ShortcutSetInterface $entity) { + $this->getOriginalObject()->deleteAssignedShortcutSets($entity); + } + + /** + * {@inheritdoc} + */ + public function getAssignedToUser($account) { + return $this->getOriginalObject()->getAssignedToUser($account); + } + + /** + * {@inheritdoc} + */ + public function countAssignedUsers(ShortcutSetInterface $shortcut_set) { + return $this->getOriginalObject()->countAssignedUsers($shortcut_set); + } + + /** + * {@inheritdoc} + */ + public function getDefaultSet(AccountInterface $account) { + return $this->getOriginalObject()->getDefaultSet($account); + } + +} diff --git a/web/modules/contrib/devel/webprofiler/src/Entity/Decorators/Config/VocabularyStorageDecorator.php b/web/modules/contrib/devel/webprofiler/src/Entity/Decorators/Config/VocabularyStorageDecorator.php new file mode 100644 index 000000000..820ddc2e7 --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/src/Entity/Decorators/Config/VocabularyStorageDecorator.php @@ -0,0 +1,19 @@ +getOriginalObject()->getToplevelTids($vids); + } + +} diff --git a/web/modules/contrib/devel/webprofiler/src/Entity/EntityDecorator.php b/web/modules/contrib/devel/webprofiler/src/Entity/EntityDecorator.php new file mode 100644 index 000000000..3cdb49d7e --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/src/Entity/EntityDecorator.php @@ -0,0 +1,24 @@ +entities; + } + +} diff --git a/web/modules/contrib/devel/webprofiler/src/Entity/EntityManagerWrapper.php b/web/modules/contrib/devel/webprofiler/src/Entity/EntityManagerWrapper.php new file mode 100644 index 000000000..cefdbeae4 --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/src/Entity/EntityManagerWrapper.php @@ -0,0 +1,263 @@ +entityTypeManager = $entity_type_manager; + } + + /** + * {@inheritdoc} + */ + public function getStorage($entity_type) { + /** @var \Drupal\Core\Config\Entity\ConfigEntityStorageInterface $handler */ + $handler = $this->getHandler($entity_type, 'storage'); + $type = ($handler instanceof ConfigEntityStorageInterface) ? 'config' : 'content'; + + if (!isset($this->loaded[$type][$entity_type])) { + $handler = $this->getStorageDecorator($entity_type, $handler); + $this->loaded[$type][$entity_type] = $handler; + } + else { + $handler = $this->loaded[$type][$entity_type]; + } + + return $handler; + } + + /** + * {@inheritdoc} + */ + public function getViewBuilder($entity_type) { + /** @var \Drupal\Core\Entity\EntityViewBuilderInterface $handler */ + $handler = $this->getHandler($entity_type, 'view_builder'); + + if ($handler instanceof EntityViewBuilderInterface) { + if (!isset($this->rendered[$entity_type])) { + $handler = new EntityViewBuilderDecorator($handler); + $this->rendered[$entity_type] = $handler; + } + else { + $handler = $this->rendered[$entity_type]; + } + } + + return $handler; + } + + /** + * @param $entity_type + * @param $handler + * + * @return \Drupal\webprofiler\Entity\EntityDecorator + */ + private function getStorageDecorator($entity_type, $handler) { + if ($handler instanceof ConfigEntityStorageInterface) { + switch ($entity_type) { + // Do not need a 'break' statement after each case-breaking 'return'. + case 'taxonomy_vocabulary': + return new VocabularyStorageDecorator($handler); + + case 'user_role': + return new RoleStorageDecorator($handler); + + case 'shortcut_set': + return new ShortcutSetStorageDecorator($handler); + + case 'image_style': + return new ImageStyleStorageDecorator($handler); + + case 'domain': + return new DomainStorageDecorator($handler); + + default: + return new ConfigEntityStorageDecorator($handler); + } + } + return $handler; + } + + /** + * @param $type + * @param $entity_type + * + * @return array + */ + public function getLoaded($type, $entity_type) { + return isset($this->loaded[$type][$entity_type]) ? $this->loaded[$type][$entity_type] : NULL; + } + + /** + * @param $entity_type + * + * @return array + */ + public function getRendered($entity_type) { + return isset($this->rendered[$entity_type]) ? $this->rendered[$entity_type] : NULL; + } + + /** + * {@inheritdoc} + */ + public function useCaches($use_caches = FALSE) { + $this->entityTypeManager->useCaches($use_caches); + } + + /** + * {@inheritdoc} + */ + public function hasDefinition($plugin_id) { + return $this->entityTypeManager->hasDefinition($plugin_id); + } + + /** + * {@inheritdoc} + */ + public function getAccessControlHandler($entity_type) { + return $this->entityTypeManager->getAccessControlHandler($entity_type); + } + + /** + * {@inheritdoc} + */ + public function clearCachedDefinitions() { + $this->entityTypeManager->clearCachedDefinitions(); + $this->loaded = NULL; + $this->rendered = NULL; + } + + /** + * {@inheritdoc} + */ + public function getListBuilder($entity_type) { + return $this->entityTypeManager->getListBuilder($entity_type); + } + + /** + * {@inheritdoc} + */ + public function getFormObject($entity_type, $operation) { + return $this->entityTypeManager->getFormObject($entity_type, $operation); + } + + /** + * {@inheritdoc} + */ + public function getRouteProviders($entity_type) { + return $this->entityTypeManager->getRouteProviders($entity_type); + } + + /** + * {@inheritdoc} + */ + public function hasHandler($entity_type, $handler_type) { + return $this->entityTypeManager->hasHandler($entity_type, $handler_type); + } + + /** + * {@inheritdoc} + */ + public function getHandler($entity_type, $handler_type) { + return $this->entityTypeManager->getHandler($entity_type, $handler_type); + } + + /** + * {@inheritdoc} + */ + public function createHandlerInstance( + $class, + EntityTypeInterface $definition = NULL + ) { + return $this->entityTypeManager->createHandlerInstance($class, $definition); + } + + /** + * {@inheritdoc} + */ + public function getDefinition($entity_type_id, $exception_on_invalid = TRUE) { + return $this->entityTypeManager->getDefinition( + $entity_type_id, + $exception_on_invalid + ); + } + + /** + * {@inheritdoc} + */ + public function getDefinitions() { + return $this->entityTypeManager->getDefinitions(); + } + + /** + * {@inheritdoc} + */ + public function createInstance($plugin_id, array $configuration = []) { + return $this->entityTypeManager->createInstance($plugin_id, $configuration); + } + + /** + * {@inheritdoc} + */ + public function getInstance(array $options) { + return $this->entityTypeManager->getInstance($options); + } + + /** + * {@inheritdoc} + */ + public function setContainer(ContainerInterface $container = NULL) { + $this->entityTypeManager->setContainer($container); + } + + /** + * {@inheritdoc} + */ + public function getActiveDefinition($entity_type_id) { + return $this->entityTypeManager->getActiveDefinition($entity_type_id); + } + + /** + * {@inheritdoc} + */ + public function getActiveFieldStorageDefinitions($entity_type_id) { + return $this->entityTypeManager->getActiveFieldStorageDefinitions($entity_type_id); + } + +} diff --git a/web/modules/contrib/devel/webprofiler/src/Entity/EntityViewBuilderDecorator.php b/web/modules/contrib/devel/webprofiler/src/Entity/EntityViewBuilderDecorator.php new file mode 100644 index 000000000..1988a6acd --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/src/Entity/EntityViewBuilderDecorator.php @@ -0,0 +1,93 @@ +entities = []; + } + + /** + * {@inheritdoc} + */ + public function buildComponents(array &$build, array $entities, array $displays, $view_mode, $langcode = NULL) { + $this->getOriginalObject() + ->buildComponents($build, $entities, $displays, $view_mode, $langcode); + } + + /** + * {@inheritdoc} + */ + public function view(EntityInterface $entity, $view_mode = 'full', $langcode = NULL) { + $this->entities[] = $entity; + + return $this->getOriginalObject()->view($entity, $view_mode, $langcode); + } + + /** + * {@inheritdoc} + */ + public function viewMultiple(array $entities = [], $view_mode = 'full', $langcode = NULL) { + $this->entities = array_merge($this->entities, $entities); + + return $this->getOriginalObject() + ->viewMultiple($entities, $view_mode, $langcode); + } + + /** + * {@inheritdoc} + */ + public function resetCache(array $entities = NULL) { + $this->getOriginalObject()->resetCache($entities); + } + + /** + * {@inheritdoc} + */ + public function viewField(FieldItemListInterface $items, $display_options = []) { + return $this->getOriginalObject()->viewField($items, $display_options); + } + + /** + * {@inheritdoc} + */ + public function viewFieldItem(FieldItemInterface $item, $display_options = []) { + return $this->getOriginalObject()->viewFieldItem($item, $display_options); + } + + /** + * {@inheritdoc} + */ + public function getCacheTags() { + return $this->getOriginalObject()->getCacheTags(); + } + + /** + * {@inheritdoc} + */ + public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) { + return new static( + $entity_type, + $container->get('entity_type.manager'), + $container->get('language_manager') + ); + } + +} diff --git a/web/modules/contrib/devel/webprofiler/src/EventDispatcher/EventDispatcherTraceableInterface.php b/web/modules/contrib/devel/webprofiler/src/EventDispatcher/EventDispatcherTraceableInterface.php new file mode 100644 index 000000000..6abe2f863 --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/src/EventDispatcher/EventDispatcherTraceableInterface.php @@ -0,0 +1,22 @@ +notCalledListeners = $listeners; + } + + /** + * {@inheritdoc} + */ + public function addListener($event_name, $listener, $priority = 0) { + parent::addListener($event_name, $listener, $priority); + $this->notCalledListeners[$event_name][$priority][] = ['callable' => $listener]; + } + + /** + * {@inheritdoc} + */ + public function dispatch($event, $event_name = NULL) { + // Temporary hack for 9.0 and 9.1 compat. See https://gitlab.com/drupalspoons/devel/-/issues/344. + if (is_string($event)) { + $event_obj = $event_name ?? new Event(); + $event_name = $event; + $event = $event_obj; + } + + $this->preDispatch($event_name, $event); + $e = $this->stopwatch->start($event_name, 'section'); + + if (isset($this->listeners[$event_name])) { + // Sort listeners if necessary. + if (isset($this->unsorted[$event_name])) { + krsort($this->listeners[$event_name]); + unset($this->unsorted[$event_name]); + } + + // Invoke listeners and resolve callables if necessary. + foreach ($this->listeners[$event_name] as $priority => &$definitions) { + foreach ($definitions as &$definition) { + if (!isset($definition['callable'])) { + $definition['callable'] = [ + $this->container->get($definition['service'][0]), + $definition['service'][1], + ]; + } + + $definition['callable']($event, $event_name, $this); + + $this->addCalledListener($definition, $event_name, $priority); + + if ($event->isPropagationStopped()) { + return $event; + } + } + } + } + + if ($e->isStarted()) { + $e->stop(); + } + + $this->postDispatch($event_name, $event); + + return $event; + } + + /** + * {@inheritdoc} + */ + public function getCalledListeners() { + return $this->calledListeners; + } + + /** + * {@inheritdoc} + */ + public function getNotCalledListeners() { + return $this->notCalledListeners; + } + + /** + * @param \Drupal\webprofiler\Stopwatch $stopwatch + */ + public function setStopwatch(Stopwatch $stopwatch) { + $this->stopwatch = $stopwatch; + } + + /** + * Called before dispatching the event. + * + * @param string $eventName + * The event name. + * @param \Symfony\Component\EventDispatcher\Event $event + * The event. + */ + protected function preDispatch($eventName, Event $event) { + switch ($eventName) { + case KernelEvents::VIEW: + case KernelEvents::RESPONSE: + // Stop only if a controller has been executed. + if ($this->stopwatch->isStarted('controller')) { + $this->stopwatch->stop('controller'); + } + break; + } + } + + /** + * Called after dispatching the event. + * + * @param string $eventName + * The event name. + * @param \Symfony\Component\EventDispatcher\Event $event + * The event. + */ + protected function postDispatch($eventName, Event $event) { + switch ($eventName) { + case KernelEvents::CONTROLLER: + $this->stopwatch->start('controller', 'section'); + break; + + case KernelEvents::RESPONSE: + $token = $event->getResponse()->headers->get('X-Debug-Token'); + try { + $this->stopwatch->stopSection($token); + } + catch (\LogicException $e) { + } + break; + + case KernelEvents::TERMINATE: + // In the special case described in the `preDispatch` method above, the + // `$token` section does not exist, then closing it throws an exception + // which must be caught. + $token = $event->getResponse()->headers->get('X-Debug-Token'); + try { + $this->stopwatch->stopSection($token); + } + catch (\LogicException $e) { + } + break; + } + } + + /** + * @param $definition + * @param $event_name + * @param $priority + */ + private function addCalledListener($definition, $event_name, $priority) { + if ($this->isClosure($definition['callable'])) { + $this->calledListeners[$event_name][$priority][] = [ + 'class' => 'Closure', + 'method' => '', + ]; + } + else { + $this->calledListeners[$event_name][$priority][] = [ + 'class' => get_class($definition['callable'][0]), + 'method' => $definition['callable'][1], + ]; + } + + foreach ($this->notCalledListeners[$event_name][$priority] as $key => $listener) { + if (isset($listener['service'])) { + if ($listener['service'][0] == $definition['service'][0] && $listener['service'][1] == $definition['service'][1]) { + unset($this->notCalledListeners[$event_name][$priority][$key]); + } + } + else { + if ($this->isClosure($listener['callable'])) { + if (is_callable($listener['callable'], TRUE, $listenerCallableName) && is_callable($definition['callable'], TRUE, $definitionCallableName)) { + if ($listenerCallableName == $definitionCallableName) { + unset($this->notCalledListeners[$event_name][$priority][$key]); + } + } + } + else { + if (get_class($listener['callable'][0]) == get_class($definition['callable'][0]) && $listener['callable'][1] == $definition['callable'][1]) { + unset($this->notCalledListeners[$event_name][$priority][$key]); + } + } + } + + } + } + + /** + * + */ + private function isClosure($t) { + return is_object($t) && ($t instanceof \Closure); + } + +} diff --git a/web/modules/contrib/devel/webprofiler/src/EventSubscriber/ProfilerSubscriber.php b/web/modules/contrib/devel/webprofiler/src/EventSubscriber/ProfilerSubscriber.php new file mode 100644 index 000000000..f52eb7420 --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/src/EventSubscriber/ProfilerSubscriber.php @@ -0,0 +1,133 @@ +profiler = $profiler; + $this->matcher = $matcher; + $this->onlyException = (bool) $onlyException; + $this->onlyMasterRequests = (bool) $onlyMasterRequests; + $this->profiles = new \SplObjectStorage(); + $this->parents = new \SplObjectStorage(); + $this->requestStack = $requestStack; + } + + /** + * Handles the onKernelException event. + */ + public function onKernelException(GetResponseForExceptionEvent $event) { + if ($this->onlyMasterRequests && !$event->isMasterRequest()) { + return; + } + + $this->exception = $event->getException(); + } + + /** + * Handles the onKernelResponse event. + */ + public function onKernelResponse(FilterResponseEvent $event) { + $master = $event->isMasterRequest(); + if ($this->onlyMasterRequests && !$master) { + return; + } + + if ($this->onlyException && NULL === $this->exception) { + return; + } + + $request = $event->getRequest(); + $exception = $this->exception; + $this->exception = NULL; + + if (NULL !== $this->matcher && !$this->matcher->matches($request)) { + return; + } + + if (!$profile = $this->profiler->collect($request, $event->getResponse(), $exception)) { + return; + } + + $this->profiles[$request] = $profile; + + $this->parents[$request] = $this->requestStack->getParentRequest(); + } + + /** + * + */ + public function onKernelFinishRequest(FinishRequestEvent $event) { + // Attach children to parents. + foreach ($this->profiles as $request) { + if (NULL !== $parentRequest = $this->parents[$request]) { + if (isset($this->profiles[$parentRequest])) { + $this->profiles[$parentRequest]->addChild($this->profiles[$request]); + } + } + } + + // Save profiles. + foreach ($this->profiles as $request) { + $this->profiler->saveProfile($this->profiles[$request]); + } + + $this->profiles = new \SplObjectStorage(); + $this->parents = new \SplObjectStorage(); + } + + /** + * + */ + public static function getSubscribedEvents() { + return [ + KernelEvents::RESPONSE => ['onKernelResponse', -100], + KernelEvents::EXCEPTION => 'onKernelException', + KernelEvents::FINISH_REQUEST => ['onKernelFinishRequest', -1024], + ]; + } + +} diff --git a/web/modules/contrib/devel/webprofiler/src/EventSubscriber/WebprofilerEventSubscriber.php b/web/modules/contrib/devel/webprofiler/src/EventSubscriber/WebprofilerEventSubscriber.php new file mode 100644 index 000000000..6325b7727 --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/src/EventSubscriber/WebprofilerEventSubscriber.php @@ -0,0 +1,98 @@ +currentUser = $currentUser; + $this->urlGenerator = $urlGenerator; + $this->renderer = $renderer; + } + + /** + * @param \Symfony\Component\HttpKernel\Event\FilterResponseEvent $event + */ + public function onKernelResponse(FilterResponseEvent $event) { + $response = $event->getResponse(); + $request = $event->getRequest(); + + if ($response->headers->has('X-Debug-Token') && NULL !== $this->urlGenerator) { + $response->headers->set( + 'X-Debug-Token-Link', + $this->urlGenerator->generate('webprofiler.dashboard', ['profile' => $response->headers->get('X-Debug-Token')]) + ); + } + + // Do not capture redirects or modify XML HTTP Requests. + if ($request->isXmlHttpRequest()) { + return; + } + + if ($this->currentUser->hasPermission('view webprofiler toolbar')) { + $this->injectToolbar($response); + } + } + + /** + * @param \Symfony\Component\HttpFoundation\Response $response + */ + protected function injectToolbar(Response $response) { + $content = $response->getContent(); + $pos = mb_strripos($content, ''); + + if (FALSE !== $pos) { + if ($token = $response->headers->get('X-Debug-Token')) { + $loader = [ + '#theme' => 'webprofiler_loader', + '#token' => $token, + '#profiler_url' => $this->urlGenerator->generate('webprofiler.toolbar', ['profile' => $token]), + ]; + + $content = mb_substr($content, 0, $pos) . $this->renderer->renderRoot($loader) . mb_substr($content, $pos); + $response->setContent($content); + } + } + } + + /** + * @return array + */ + public static function getSubscribedEvents() { + return [ + KernelEvents::RESPONSE => ['onKernelResponse', -128], + ]; + } + +} diff --git a/web/modules/contrib/devel/webprofiler/src/Form/ConfigForm.php b/web/modules/contrib/devel/webprofiler/src/Form/ConfigForm.php new file mode 100644 index 000000000..e16d77a79 --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/src/Form/ConfigForm.php @@ -0,0 +1,239 @@ +get('config.factory'), + $container->get('profiler'), + $container->get('profiler.storage_manager'), + $container->getParameter('data_collector.templates') + ); + } + + /** + * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory + * @param \Symfony\Component\HttpKernel\Profiler\Profiler $profiler + * @param \Drupal\webprofiler\Profiler\ProfilerStorageManager $storageManager + * @param array $templates + */ + public function __construct(ConfigFactoryInterface $config_factory, Profiler $profiler, ProfilerStorageManager $storageManager, $templates) { + parent::__construct($config_factory); + + $this->profiler = $profiler; + $this->templates = $templates; + $this->storageManager = $storageManager; + } + + /** + * {@inheritdoc} + */ + public function getFormId() { + return 'webprofiler_config'; + } + + /** + * {@inheritdoc} + */ + public function buildForm(array $form, FormStateInterface $form_state) { + $config = $this->config('webprofiler.config'); + + $form['purge_on_cache_clear'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Purge on cache clear'), + '#description' => $this->t('Deletes all profiler files during cache clear.'), + '#default_value' => $config->get('purge_on_cache_clear'), + ]; + + $storages = $this->storageManager->getStorages(); + + $form['storage'] = [ + '#type' => 'select', + '#title' => $this->t('Storage backend'), + '#description' => $this->t('Choose were to store profiler data.'), + '#options' => $storages, + '#default_value' => $config->get('storage'), + ]; + + $form['exclude'] = [ + '#type' => 'textarea', + '#title' => $this->t('Exclude'), + '#default_value' => $config->get('exclude'), + '#description' => $this->t('Paths to exclude for profiling. One path per line.'), + ]; + + $form['active_toolbar_items'] = [ + '#type' => 'checkboxes', + '#title' => $this->t('Active toolbar items'), + '#options' => $this->getCollectors(), + '#description' => $this->t('Choose which items to show into the toolbar.'), + '#default_value' => $config->get('active_toolbar_items'), + ]; + + $form['ide_settings'] = [ + '#type' => 'details', + '#title' => $this->t('IDE settings'), + '#open' => FALSE, + ]; + + $form['ide_settings']['ide_link'] = [ + '#type' => 'textfield', + '#title' => $this->t('IDE link'), + '#description' => $this->t('IDE link for open files.'), + '#default_value' => $config->get('ide_link'), + ]; + + $form['ide_settings']['ide_link_remote'] = [ + '#type' => 'textfield', + '#title' => $this->t('IDE link remote path'), + '#description' => $this->t('The path of the remote docroot. Leave blank if the docroot is on the same machine of the IDE. No trailing slash.'), + '#default_value' => $config->get('ide_link_remote'), + ]; + + $form['ide_settings']['ide_link_local'] = [ + '#type' => 'textfield', + '#title' => $this->t('IDE link local path'), + '#description' => $this->t('The path of the local docroot. Leave blank if the docroot is on the same machine of IDE. No trailing slash.'), + '#default_value' => $config->get('ide_link_local'), + ]; + + $form['database'] = [ + '#type' => 'details', + '#title' => $this->t('Database settings'), + '#open' => FALSE, + '#states' => [ + 'visible' => [ + [ + 'input[name="active_toolbar_items[database]"]' => ['checked' => TRUE], + ], + ], + ], + ]; + + $form['database']['query_sort'] = [ + '#type' => 'radios', + '#title' => $this->t('Sort query log'), + '#options' => ['source' => 'by source', 'duration' => 'by duration'], + '#description' => $this->t('The query table can be sorted in the order that the queries were executed or by descending duration.'), + '#default_value' => $config->get('query_sort'), + ]; + + $form['database']['query_highlight'] = [ + '#type' => 'number', + '#title' => $this->t('Slow query highlighting'), + '#description' => $this->t('Enter an integer in milliseconds. Any query which takes longer than this many milliseconds will be highlighted in the query log. This indicates a possibly inefficient query, or a candidate for caching.'), + '#default_value' => $config->get('query_highlight'), + '#min' => 0, + ]; + + $storageId = $this->config('webprofiler.config')->get('storage'); + $storage = $this->storageManager->getStorage($storageId); + + $form['purge'] = [ + '#type' => 'details', + '#title' => $this->t('Purge profiles'), + '#open' => FALSE, + ]; + + $form['purge']['actions'] = ['#type' => 'actions']; + $form['purge']['actions']['purge'] = [ + '#type' => 'submit', + '#value' => $this->t('Purge'), + '#submit' => [[$this, 'purge']], + ]; + + $form['purge']['purge-help'] = [ + '#type' => 'inline_template', + '#template' => '
      {{ message }}
      ', + '#context' => [ + 'message' => $this->t('Purge %storage profiles.', ['%storage' => $storage['title']]), + ], + ]; + + return parent::buildForm($form, $form_state); + } + + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, FormStateInterface $form_state) { + $this->config('webprofiler.config') + ->set('purge_on_cache_clear', $form_state->getValue('purge_on_cache_clear')) + ->set('storage', $form_state->getValue('storage')) + ->set('exclude', $form_state->getValue('exclude')) + ->set('active_toolbar_items', $form_state->getValue('active_toolbar_items')) + ->set('ide_link', $form_state->getValue('ide_link')) + ->set('ide_link_remote', $form_state->getValue('ide_link_remote')) + ->set('ide_link_local', $form_state->getValue('ide_link_local')) + ->set('query_sort', $form_state->getValue('query_sort')) + ->set('query_highlight', $form_state->getValue('query_highlight')) + ->save(); + + parent::submitForm($form, $form_state); + } + + /** + * Purges profiles. + */ + public function purge(array &$form, FormStateInterface $form_state) { + $this->profiler->purge(); + $this->messenger()->addMessage($this->t('Profiles purged')); + } + + /** + * @return array + */ + private function getCollectors() { + $options = []; + foreach ($this->templates as $template) { + // Drupal collector should not be disabled. + if ($template[0] != 'drupal') { + $options[$template[0]] = $template[2]; + } + } + + asort($options); + + return $options; + } + + /** + * {@inheritdoc} + */ + public function getEditableConfigNames() { + return [ + 'webprofiler.config', + ]; + } + +} diff --git a/web/modules/contrib/devel/webprofiler/src/Form/FormBuilderWrapper.php b/web/modules/contrib/devel/webprofiler/src/Form/FormBuilderWrapper.php new file mode 100644 index 000000000..723b4e9e8 --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/src/Form/FormBuilderWrapper.php @@ -0,0 +1,62 @@ +buildForms; + } + + /** + * {@inheritdoc} + */ + public function prepareForm($form_id, &$form, FormStateInterface &$form_state) { + parent::prepareForm($form_id, $form, $form_state); + + if (!$this->buildForms) { + $this->buildForms = []; + } + + $elements = []; + foreach ($form as $key => $value) { + if (strpos($key, '#') !== 0) { + $elements[$key]['#title'] = isset($value['#title']) ? $value['#title'] : NULL; + $elements[$key]['#access'] = isset($value['#access']) ? $value['#access'] : NULL; + $elements[$key]['#type'] = isset($value['#type']) ? $value['#type'] : NULL; + } + } + + $buildInfo = $form_state->getBuildInfo(); + + $class = get_class($buildInfo['callback_object']); + $method = new \ReflectionMethod($class, 'buildForm'); + + $this->buildForms[$buildInfo['form_id']] = [ + 'class' => [ + 'class' => $class, + 'method' => 'buildForm', + 'file' => $method->getFilename(), + 'line' => $method->getStartLine(), + ], + 'form' => $elements, + ]; + + return $form; + } + +} diff --git a/web/modules/contrib/devel/webprofiler/src/Form/ProfilesFilterForm.php b/web/modules/contrib/devel/webprofiler/src/Form/ProfilesFilterForm.php new file mode 100644 index 000000000..17137374f --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/src/Form/ProfilesFilterForm.php @@ -0,0 +1,87 @@ + 'textfield', + '#title' => $this->t('IP'), + '#size' => 30, + '#default_value' => $this->getRequest()->query->get('ip'), + '#prefix' => '
      ', + ]; + + $form['url'] = [ + '#type' => 'textfield', + '#title' => $this->t('Url'), + '#size' => 30, + '#default_value' => $this->getRequest()->query->get('url'), + ]; + + $form['method'] = [ + '#type' => 'select', + '#title' => $this->t('Method'), + '#options' => ['GET' => 'GET', 'POST' => 'POST'], + '#default_value' => $this->getRequest()->query->get('method'), + ]; + + $limits = [10, 50, 100]; + $form['limit'] = [ + '#type' => 'select', + '#title' => $this->t('Limit'), + '#options' => array_combine($limits, $limits), + '#default_value' => $this->getRequest()->query->get('limit'), + ]; + + $form['actions'] = ['#type' => 'actions']; + $form['actions']['filter'] = [ + '#type' => 'submit', + '#value' => $this->t('Filter'), + '#attributes' => ['class' => ['button--primary']], + '#suffix' => '
      ', + ]; + + return $form; + } + + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, FormStateInterface $form_state) { + $ip = $form_state->getValue('ip'); + $url = $form_state->getValue('url'); + $method = $form_state->getValue('method'); + $limit = $form_state->getValue('limit'); + + $url = new Url('webprofiler.admin_list', [], [ + 'query' => [ + 'ip' => $ip, + 'url' => $url, + 'method' => $method, + 'limit' => $limit, + ], + ]); + + $form_state->setRedirectUrl($url); + } + +} diff --git a/web/modules/contrib/devel/webprofiler/src/Frontend/PerformanceTimingData.php b/web/modules/contrib/devel/webprofiler/src/Frontend/PerformanceTimingData.php new file mode 100644 index 000000000..2c5a2b96d --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/src/Frontend/PerformanceTimingData.php @@ -0,0 +1,82 @@ +data = $data; + } + + /** + * @return int + */ + public function getDNSTiming() { + if (isset($this->data['domainLookupEnd']) && isset($this->data['domainLookupStart'])) { + return $this->data['domainLookupEnd'] - $this->data['domainLookupStart']; + } + else { + return 0; + } + } + + /** + * @return int + */ + public function getTCPTiming() { + if (isset($this->data['connectEnd']) && isset($this->data['connectStart'])) { + return $this->data['connectEnd'] - $this->data['connectStart']; + } + else { + return 0; + } + } + + /** + * @return int + */ + public function getTtfbTiming() { + if (isset($this->data['responseStart']) && isset($this->data['connectEnd'])) { + return $this->data['responseStart'] - $this->data['connectEnd']; + } + else { + return 0; + } + } + + /** + * @return int + */ + public function getDataTiming() { + if (isset($this->data['responseEnd']) && isset($this->data['responseStart'])) { + return $this->data['responseEnd'] - $this->data['responseStart']; + } + else { + return 0; + } + } + + /** + * @return int + */ + public function getDomTiming() { + if (isset($this->data['loadEventStart']) && isset($this->data['responseEnd'])) { + return $this->data['loadEventStart'] - $this->data['responseEnd']; + } + else { + return 0; + } + } + +} diff --git a/web/modules/contrib/devel/webprofiler/src/Helper/ClassShortener.php b/web/modules/contrib/devel/webprofiler/src/Helper/ClassShortener.php new file mode 100644 index 000000000..c5277129c --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/src/Helper/ClassShortener.php @@ -0,0 +1,35 @@ + $part) { + if ($key < $size) { + $result[] = substr($part, 0, 1); + } + else { + $result[] = $part; + } + } + + return new FormattableMarkup("@short", [ + '@class' => $class, + '@short' => implode('\\', $result), + ]); + } + +} diff --git a/web/modules/contrib/devel/webprofiler/src/Helper/ClassShortenerInterface.php b/web/modules/contrib/devel/webprofiler/src/Helper/ClassShortenerInterface.php new file mode 100644 index 000000000..14f75c198 --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/src/Helper/ClassShortenerInterface.php @@ -0,0 +1,17 @@ +config_factory = $config_factory; + } + + /** + * {@inheritdoc} + */ + public function generateLink($file, $line) { + $ide_link = $this->config_factory->get('webprofiler.config') + ->get('ide_link'); + $ide_link_remote = $this->config_factory->get('webprofiler.config') + ->get('ide_link_remote'); + $ide_link_local = $this->config_factory->get('webprofiler.config') + ->get('ide_link_local'); + + $file = str_replace($ide_link_remote, $ide_link_local, $file); + + return new FormattableMarkup($ide_link, ['@file' => $file, '@line' => $line]); + } + +} diff --git a/web/modules/contrib/devel/webprofiler/src/Helper/IdeLinkGeneratorInterface.php b/web/modules/contrib/devel/webprofiler/src/Helper/IdeLinkGeneratorInterface.php new file mode 100644 index 000000000..7d890c7f0 --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/src/Helper/IdeLinkGeneratorInterface.php @@ -0,0 +1,18 @@ +completedRequests = []; + $this->failedRequests = []; + } + + /** + * {@inheritdoc} + */ + public function __invoke() { + return function ($handler) { + return function (RequestInterface $request, array $options) use ($handler) { + + // If on_stats callback is already set then save it + // and call it after ours. + if (isset($options['on_stats'])) { + $next = $options['on_stats']; + } + else { + $next = function (TransferStats $stats) {}; + } + + $options['on_stats'] = function (TransferStats $stats) use ($request, $next) { + $request->stats = $stats; + $next($stats); + }; + + return $handler($request, $options)->then( + function ($response) use ($request) { + + $this->completedRequests[] = [ + 'request' => $request, + 'response' => $response, + ]; + + return $response; + }, + function ($reason) use ($request) { + $response = $reason instanceof RequestException + ? $reason->getResponse() + : NULL; + + $this->failedRequests[] = [ + 'request' => $request, + 'response' => $response, + 'message' => $reason->getMessage(), + ]; + + return \GuzzleHttp\Promise\rejection_for($reason); + } + ); + }; + }; + } + + /** + * @return array + */ + public function getCompletedRequests() { + return $this->completedRequests; + } + + /** + * @return array + */ + public function getFailedRequests() { + return $this->failedRequests; + } + +} diff --git a/web/modules/contrib/devel/webprofiler/src/Mail/MailManagerWrapper.php b/web/modules/contrib/devel/webprofiler/src/Mail/MailManagerWrapper.php new file mode 100644 index 000000000..e83c3bca8 --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/src/Mail/MailManagerWrapper.php @@ -0,0 +1,106 @@ +alterInfo('mail_backend_info'); + $this->setCacheBackend($cache_backend, 'mail_backend_plugins'); + $this->configFactory = $config_factory; + $this->loggerFactory = $logger_factory; + $this->stringTranslation = $string_translation; + $this->dataCollector = $dataCollector; + $this->mailManager = $mailManager; + } + + /** + * {@inheritdoc} + */ + public function mail($module, $key, $to, $langcode, $params = [], $reply = NULL, $send = TRUE) { + $message = $this->mailManager->mail($module, $key, $to, $langcode, $params, $reply, $send); + + $instance = $this->mailManager->getInstance(['module' => $module, 'key' => $key]); + $this->dataCollector->addMessage($message, $instance); + + return $message; + } + + /** + * {@inheritdoc} + * + * Must call getInstance on the decorated MailManager. + * + * @see https://www.drupal.org/node/2625554 + */ + public function getInstance(array $options) { + return $this->mailManager->getInstance($options); + } + +} diff --git a/web/modules/contrib/devel/webprofiler/src/Profiler/DatabaseProfilerStorage.php b/web/modules/contrib/devel/webprofiler/src/Profiler/DatabaseProfilerStorage.php new file mode 100644 index 000000000..37c025944 --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/src/Profiler/DatabaseProfilerStorage.php @@ -0,0 +1,160 @@ +database = $database; + } + + /** + * {@inheritdoc} + */ + public function find($ip, $url, $limit, $method, $start = NULL, $end = NULL): array { + $select = $this->database->select('webprofiler', 'wp', ['fetch' => \PDO::FETCH_ASSOC]); + + if (NULL === $start) { + $start = 0; + } + + if (NULL === $end) { + $end = time(); + } + + if ($ip = preg_replace('/[^\d\.]/', '', $ip)) { + $select->condition('ip', '%' . $this->database->escapeLike($ip) . '%', 'LIKE'); + } + + if ($url) { + $select->condition('url', '%' . $this->database->escapeLike(addcslashes($url, '%_\\')) . '%', 'LIKE'); + } + + if ($method) { + $select->condition('method', $method); + } + + if (!empty($start)) { + $select->condition('time', $start, '>='); + } + + if (!empty($end)) { + $select->condition('time', $end, '<='); + } + + $select->fields('wp', [ + 'token', + 'ip', + 'method', + 'url', + 'time', + 'parent', + 'status_code', + ]); + $select->orderBy('time', 'DESC'); + $select->range(0, $limit); + return $select->execute() + ->fetchAllAssoc('token'); + } + + /** + * {@inheritdoc} + */ + public function read($token): ?Profile { + $record = $this->database->select('webprofiler', 'w') + ->fields('w') + ->condition('token', $token) + ->execute() + ->fetch(); + if (isset($record->data)) { + return $this->createProfileFromData($token, $record); + } + } + + /** + * {@inheritdoc} + */ + public function write(Profile $profile): bool { + $args = [ + 'token' => $profile->getToken(), + 'parent' => $profile->getParentToken(), + 'data' => base64_encode(serialize($profile->getCollectors())), + 'ip' => $profile->getIp(), + 'method' => $profile->getMethod(), + 'url' => $profile->getUrl(), + 'time' => $profile->getTime(), + 'created_at' => time(), + 'status_code' => $profile->getStatusCode(), + ]; + + try { + $query = $this->database->select('webprofiler', 'w') + ->fields('w', ['token']); + $query->condition('token', $profile->getToken()); + $count = $query->countQuery()->execute()->fetchAssoc(); + + if ($count['expression']) { + $this->database->update('webprofiler') + ->fields($args) + ->condition('token', $profile->getToken()) + ->execute(); + } + else { + $this->database->insert('webprofiler')->fields($args)->execute(); + } + + $status = TRUE; + } + catch (\Exception $e) { + $status = FALSE; + } + + return $status; + } + + /** + * {@inheritdoc} + */ + public function purge() { + $this->database->truncate('webprofiler')->execute(); + } + + /** + * @param string $token + * @param $data + * + * @return \Symfony\Component\HttpKernel\Profiler\Profile + */ + private function createProfileFromData($token, $data) { + $profile = new Profile($token); + $profile->setIp($data->ip); + $profile->setMethod($data->method); + $profile->setUrl($data->url); + $profile->setTime($data->time); + $profile->setCollectors(unserialize(base64_decode($data->data))); + $profile->setStatusCode($data->status_code); + + return $profile; + } + +} diff --git a/web/modules/contrib/devel/webprofiler/src/Profiler/FileProfilerStorage.php b/web/modules/contrib/devel/webprofiler/src/Profiler/FileProfilerStorage.php new file mode 100644 index 000000000..cec216f23 --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/src/Profiler/FileProfilerStorage.php @@ -0,0 +1,28 @@ +setIp($data['ip']); + $profile->setMethod($data['method']); + $profile->setUrl($data['url']); + $profile->setTime($data['time']); + $profile->setCollectors($data['data']); + $profile->setStatusCode($data['status_code']); + + return $profile; + } + +} diff --git a/web/modules/contrib/devel/webprofiler/src/Profiler/Profiler.php b/web/modules/contrib/devel/webprofiler/src/Profiler/Profiler.php new file mode 100644 index 000000000..2cd58dceb --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/src/Profiler/Profiler.php @@ -0,0 +1,78 @@ +localStorage = $storage; + $this->localLogger = $logger; + + $this->config = $config; + $this->activeToolbarItems = $this->config->get('webprofiler.config') + ->get('active_toolbar_items'); + } + + /** + * {@inheritdoc} + */ + public function add(DataCollectorInterface $collector) { + // Drupal collector should not be disabled. + if ($collector->getName() == 'drupal') { + parent::add($collector); + } + else { + if ($this->activeToolbarItems && array_key_exists($collector->getName(), $this->activeToolbarItems) && $this->activeToolbarItems[$collector->getName()] !== '0') { + parent::add($collector); + } + } + } + + /** + * @param \Symfony\Component\HttpKernel\Profiler\Profile $profile + * + * @return bool + */ + public function updateProfile(Profile $profile) { + if (!($ret = $this->localStorage->write($profile)) && NULL !== $this->localLogger) { + $this->localLogger->warning('Unable to store the profiler information.'); + } + + return $ret; + } + +} diff --git a/web/modules/contrib/devel/webprofiler/src/Profiler/ProfilerStorageFactory.php b/web/modules/contrib/devel/webprofiler/src/Profiler/ProfilerStorageFactory.php new file mode 100644 index 000000000..eaae5ec91 --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/src/Profiler/ProfilerStorageFactory.php @@ -0,0 +1,26 @@ +get('webprofiler.config') + ->get('storage') ?: 'profiler.database_storage'; + + return $container->get($storage); + } + +} diff --git a/web/modules/contrib/devel/webprofiler/src/Profiler/ProfilerStorageManager.php b/web/modules/contrib/devel/webprofiler/src/Profiler/ProfilerStorageManager.php new file mode 100644 index 000000000..f055dd28a --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/src/Profiler/ProfilerStorageManager.php @@ -0,0 +1,52 @@ +storages as $id => $storage) { + $output[$id] = $storage['title']; + } + + return $output; + } + + /** + * @param $id + * + * @return array + */ + public function getStorage($id) { + return $this->storages[$id]; + } + + /** + * @param $id + * @param $title + * @param \Symfony\Component\HttpKernel\Profiler\ProfilerStorageInterface $storage + */ + public function addStorage($id, $title, ProfilerStorageInterface $storage) { + $this->storages[$id] = [ + 'title' => $title, + 'class' => $storage, + ]; + } + +} diff --git a/web/modules/contrib/devel/webprofiler/src/Profiler/TemplateManager.php b/web/modules/contrib/devel/webprofiler/src/Profiler/TemplateManager.php new file mode 100644 index 000000000..d4de27455 --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/src/Profiler/TemplateManager.php @@ -0,0 +1,131 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Drupal\webprofiler\Profiler; + +use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; +use Symfony\Component\HttpKernel\Profiler\Profiler as SymfonyProfiler; +use Symfony\Component\HttpKernel\Profiler\Profile; + +/** + * Profiler Templates Manager. + */ +class TemplateManager { + + /** + * @var \Twig_Environment + */ + protected $twig; + + /** + * @var \Twig_Loader_Chain + */ + protected $twigLoader; + + /** + * @var array + */ + protected $templates; + + /** + * @var \Symfony\Component\HttpKernel\Profiler\Profiler + */ + protected $profiler; + + /** + * Constructor. + * + * @param \Symfony\Component\HttpKernel\Profiler\Profiler $profiler + * @param \Twig_Environment $twig + * @param \Twig_Loader_Chain $twigLoader + * @param array $templates + */ + public function __construct(SymfonyProfiler $profiler, \Twig_Environment $twig, \Twig_Loader_Chain $twigLoader, array $templates) { + $this->profiler = $profiler; + $this->twig = $twig; + $this->twigLoader = $twigLoader; + $this->templates = $templates; + } + + /** + * Gets the template name for a given panel. + * + * @param \Symfony\Component\HttpKernel\Profiler\Profile $profile + * @param string $panel + * + * @return mixed + * + * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException + */ + public function getName(Profile $profile, $panel) { + $templates = $this->getNames($profile); + + if (!isset($templates[$panel])) { + throw new NotFoundHttpException(sprintf('Panel "%s" is not registered in profiler or is not present in viewed profile.', $panel)); + } + + return $templates[$panel]; + } + + /** + * Gets the templates for a given profile. + * + * @param \Symfony\Component\HttpKernel\Profiler\Profile $profile + * + * @return array + */ + public function getTemplates(Profile $profile) { + $templates = $this->getNames($profile); + foreach ($templates as $name => $template) { + $templates[$name] = $this->twig->loadTemplate($template); + } + + return $templates; + } + + /** + * Gets template names of templates that are present in the viewed profile. + * + * @param \Symfony\Component\HttpKernel\Profiler\Profile $profile + * + * @return array + * + * @throws \UnexpectedValueException + */ + protected function getNames(Profile $profile) { + $templates = []; + + foreach ($this->templates as $arguments) { + if (NULL === $arguments) { + continue; + } + + list($name, $template) = $arguments; + + if (!$this->profiler->has($name) || !$profile->hasCollector($name)) { + continue; + } + + if ('.html.twig' === substr($template, -10)) { + $template = substr($template, 0, -10); + } + + if (!$this->twigLoader->exists($template . '.html.twig')) { + throw new \UnexpectedValueException(sprintf('The profiler template "%s.html.twig" for data collector "%s" does not exist.', $template, $name)); + } + + $templates[$name] = $template . '.html.twig'; + } + + return $templates; + } + +} diff --git a/web/modules/contrib/devel/webprofiler/src/ProxyClass/Command/ListCommand.php b/web/modules/contrib/devel/webprofiler/src/ProxyClass/Command/ListCommand.php new file mode 100644 index 000000000..e8e22c844 --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/src/ProxyClass/Command/ListCommand.php @@ -0,0 +1,325 @@ +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 showMessage($output, $message, $type = 'info') { + return $this->lazyLoadItself()->showMessage($output, $message, $type); + } + + /** + * {@inheritdoc} + */ + public function ignoreValidationErrors() { + return $this->lazyLoadItself()->ignoreValidationErrors(); + } + + /** + * {@inheritdoc} + */ + public function setApplication(\Symfony\Component\Console\Application $application = NULL) { + return $this->lazyLoadItself()->setApplication($application); + } + + /** + * {@inheritdoc} + */ + public function setHelperSet(\Symfony\Component\Console\Helper\HelperSet $helperSet) { + return $this->lazyLoadItself()->setHelperSet($helperSet); + } + + /** + * {@inheritdoc} + */ + public function getHelperSet() { + return $this->lazyLoadItself()->getHelperSet(); + } + + /** + * {@inheritdoc} + */ + public function getApplication() { + return $this->lazyLoadItself()->getApplication(); + } + + /** + * {@inheritdoc} + */ + public function isEnabled() { + return $this->lazyLoadItself()->isEnabled(); + } + + /** + * {@inheritdoc} + */ + public function run(\Symfony\Component\Console\Input\InputInterface $input, \Symfony\Component\Console\Output\OutputInterface $output) { + return $this->lazyLoadItself()->run($input, $output); + } + + /** + * {@inheritdoc} + */ + public function setCode(callable $code) { + return $this->lazyLoadItself()->setCode($code); + } + + /** + * {@inheritdoc} + */ + public function mergeApplicationDefinition($mergeArgs = TRUE) { + return $this->lazyLoadItself()->mergeApplicationDefinition($mergeArgs); + } + + /** + * {@inheritdoc} + */ + public function setDefinition($definition) { + return $this->lazyLoadItself()->setDefinition($definition); + } + + /** + * {@inheritdoc} + */ + public function getDefinition() { + return $this->lazyLoadItself()->getDefinition(); + } + + /** + * {@inheritdoc} + */ + public function getNativeDefinition() { + return $this->lazyLoadItself()->getNativeDefinition(); + } + + /** + * {@inheritdoc} + */ + public function addArgument($name, $mode = NULL, $description = '', $default = NULL) { + return $this->lazyLoadItself() + ->addArgument($name, $mode, $description, $default); + } + + /** + * {@inheritdoc} + */ + public function addOption($name, $shortcut = NULL, $mode = NULL, $description = '', $default = NULL) { + return $this->lazyLoadItself() + ->addOption($name, $shortcut, $mode, $description, $default); + } + + /** + * {@inheritdoc} + */ + public function setName($name) { + return $this->lazyLoadItself()->setName($name); + } + + /** + * {@inheritdoc} + */ + public function setProcessTitle($title) { + return $this->lazyLoadItself()->setProcessTitle($title); + } + + /** + * {@inheritdoc} + */ + public function getName() { + return $this->lazyLoadItself()->getName(); + } + + /** + * {@inheritdoc} + */ + public function setHidden($hidden) { + return $this->lazyLoadItself()->setHidden($hidden); + } + + /** + * {@inheritdoc} + */ + public function isHidden() { + return $this->lazyLoadItself()->isHidden(); + } + + /** + * {@inheritdoc} + */ + public function setDescription($description) { + return $this->lazyLoadItself()->setDescription($description); + } + + /** + * {@inheritdoc} + */ + public function getDescription() { + return $this->lazyLoadItself()->getDescription(); + } + + /** + * {@inheritdoc} + */ + public function setHelp($help) { + return $this->lazyLoadItself()->setHelp($help); + } + + /** + * {@inheritdoc} + */ + public function getHelp() { + return $this->lazyLoadItself()->getHelp(); + } + + /** + * {@inheritdoc} + */ + public function getProcessedHelp() { + return $this->lazyLoadItself()->getProcessedHelp(); + } + + /** + * {@inheritdoc} + */ + public function setAliases($aliases) { + return $this->lazyLoadItself()->setAliases($aliases); + } + + /** + * {@inheritdoc} + */ + public function getAliases() { + return $this->lazyLoadItself()->getAliases(); + } + + /** + * {@inheritdoc} + */ + public function getSynopsis($short = FALSE) { + return $this->lazyLoadItself()->getSynopsis($short); + } + + /** + * {@inheritdoc} + */ + public function addUsage($usage) { + return $this->lazyLoadItself()->addUsage($usage); + } + + /** + * {@inheritdoc} + */ + public function getUsages() { + return $this->lazyLoadItself()->getUsages(); + } + + /** + * {@inheritdoc} + */ + public function getHelper($name) { + return $this->lazyLoadItself()->getHelper($name); + } + + /** + * {@inheritdoc} + */ + public function setContainer($container) { + return $this->lazyLoadItself()->setContainer($container); + } + + /** + * {@inheritdoc} + */ + public function has($key) { + return $this->lazyLoadItself()->has($key); + } + + /** + * {@inheritdoc} + */ + public function get($key) { + return $this->lazyLoadItself()->get($key); + } + + /** + * {@inheritdoc} + */ + public function setTranslator($translator) { + return $this->lazyLoadItself()->setTranslator($translator); + } + + /** + * {@inheritdoc} + */ + public function trans($key) { + return $this->lazyLoadItself()->trans($key); + } + + } + +} diff --git a/web/modules/contrib/devel/webprofiler/src/RequestMatcher/WebprofilerRequestMatcher.php b/web/modules/contrib/devel/webprofiler/src/RequestMatcher/WebprofilerRequestMatcher.php new file mode 100644 index 000000000..4eaca9be9 --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/src/RequestMatcher/WebprofilerRequestMatcher.php @@ -0,0 +1,51 @@ +configFactory = $configFactory; + $this->pathMatcher = $pathMatcher; + } + + /** + * {@inheritdoc} + */ + public function matches(Request $request) { + $path = $request->getPathInfo(); + + $patterns = $this->configFactory->get('webprofiler.config')->get('exclude'); + + // Never add Webprofiler to phpinfo page. + $patterns .= "\r\n/admin/reports/status/php"; + + // Never add Webprofiler to uninstall confirm page. + $patterns .= "\r\n/admin/modules/uninstall/*"; + + return !$this->pathMatcher->matchPath($path, $patterns); + } + +} diff --git a/web/modules/contrib/devel/webprofiler/src/Routing/TokenConverter.php b/web/modules/contrib/devel/webprofiler/src/Routing/TokenConverter.php new file mode 100644 index 000000000..7a13b6372 --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/src/Routing/TokenConverter.php @@ -0,0 +1,47 @@ +loadProfile($value); + + if (NULL === $profile) { + return NULL; + } + + return $profile; + } + + /** + * {@inheritdoc} + */ + public function applies($definition, $name, Route $route) { + if (!empty($definition['type']) && $definition['type'] === 'webprofiler:token') { + return TRUE; + } + return FALSE; + } + +} diff --git a/web/modules/contrib/devel/webprofiler/src/StackMiddleware/WebprofilerMiddleware.php b/web/modules/contrib/devel/webprofiler/src/StackMiddleware/WebprofilerMiddleware.php new file mode 100644 index 000000000..fd3a04a46 --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/src/StackMiddleware/WebprofilerMiddleware.php @@ -0,0 +1,41 @@ +httpKernel = $http_kernel; + } + + /** + * {@inheritdoc} + */ + public function handle(Request $request, $type = self::MASTER_REQUEST, $catch = TRUE) { + foreach (Database::getAllConnectionInfo() as $key => $info) { + Database::startLog('webprofiler', $key); + } + return $this->httpKernel->handle($request, $type, $catch); + } + +} diff --git a/web/modules/contrib/devel/webprofiler/src/State/StateWrapper.php b/web/modules/contrib/devel/webprofiler/src/State/StateWrapper.php new file mode 100644 index 000000000..23f930ad4 --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/src/State/StateWrapper.php @@ -0,0 +1,125 @@ +state = $state; + $this->dataCollector = $dataCollector; + } + + /** + * {@inheritdoc} + */ + public function get($key, $default = NULL) { + $this->dataCollector->addState($key); + + return $this->state->get($key, $default); + } + + /** + * {@inheritdoc} + */ + public function getMultiple(array $keys) { + foreach ($keys as $key) { + $this->dataCollector->addState($key); + } + + return $this->state->getMultiple($keys); + } + + /** + * {@inheritdoc} + */ + public function set($key, $value) { + $this->state->set($key, $value); + } + + /** + * {@inheritdoc} + */ + public function setMultiple(array $data) { + $this->state->setMultiple($data); + } + + /** + * {@inheritdoc} + */ + public function delete($key) { + $this->state->delete($key); + } + + /** + * {@inheritdoc} + */ + public function deleteMultiple(array $keys) { + $this->state->deleteMultiple($keys); + } + + /** + * {@inheritdoc} + */ + public function resetCache() { + $this->state->resetCache(); + } + + /** + * {@inheritdoc} + */ + protected function resolveCacheMiss($key) { + return $this->state->resolveCacheMiss($key); + } + + /** + * {@inheritdoc} + */ + public function destruct() { + $this->updateCache(); + } + + /** + * Passes through all non-tracked calls onto the decorated object. + * + * @param string $method + * The called method. + * @param mixed $args + * The passed in arguments. + * + * @return mixed + * The return argument of the call. + */ + public function __call($method, $args) { + return call_user_func_array([$this->state, $method], $args); + } + +} diff --git a/web/modules/contrib/devel/webprofiler/src/Stopwatch.php b/web/modules/contrib/devel/webprofiler/src/Stopwatch.php new file mode 100644 index 000000000..f9b82b405 --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/src/Stopwatch.php @@ -0,0 +1,609 @@ +sections = $this->activeSections = ['__root__' => new Section('__root__')]; + } + + /** + * Creates a new section or re-opens an existing section. + * + * @param string|null $id + * The id of the session to re-open, null to create a new one. + * + * @throws \LogicException + * When the section to re-open is not reachable. + */ + public function openSection($id = NULL) { + $current = end($this->activeSections); + + if (NULL !== $id && NULL === $current->get($id)) { + throw new \LogicException(sprintf('The section "%s" has been started at an other level and can not be opened.', $id)); + } + + $this->start('__section__.child', 'section'); + $this->activeSections[] = $current->open($id); + $this->start('__section__'); + } + + /** + * Stops the last started section. + * + * The id parameter is used to retrieve the events from this section. + * + * @param string $id + * The identifier of the section. + * + * @throws \LogicException + * When there's no started section to be stopped. + * + * @see getSectionEvents + */ + public function stopSection($id) { + $this->stop('__section__'); + + if (1 == count($this->activeSections)) { + throw new \LogicException('There is no started section to stop.'); + } + + $this->sections[$id] = array_pop($this->activeSections)->setId($id); + $this->stop('__section__.child'); + } + + /** + * Starts an event. + * + * @param string $name + * The event name. + * @param string $category + * The event category. + * + * @return \Symfony\Component\Stopwatch\StopwatchEvent + * A StopwatchEvent instance + */ + public function start($name, $category = NULL) { + return end($this->activeSections)->startEvent($name, $category); + } + + /** + * Checks if the named event was started. + * + * @param string $name + * The event name. + * + * @return bool + * True if the event was started. + */ + public function isStarted($name) { + return end($this->activeSections)->isEventStarted($name); + } + + /** + * Stops an event. + * + * @param string $name + * The event name. + * + * @return \Symfony\Component\Stopwatch\StopwatchEvent + * A StopwatchEvent instance + */ + public function stop($name) { + return end($this->activeSections)->stopEvent($name); + } + + /** + * Stops then restarts an event. + * + * @param string $name + * The event name. + * + * @return \Symfony\Component\Stopwatch\StopwatchEvent + * A StopwatchEvent instance + */ + public function lap($name) { + return end($this->activeSections)->stopEvent($name)->start(); + } + + /** + * Gets all events for a given section. + * + * @param string $id + * A section identifier. + * + * @return \Symfony\Component\Stopwatch\StopwatchEvent[] + * An array of StopwatchEvent instances + */ + public function getSectionEvents($id) { + return isset($this->sections[$id]) ? $this->sections[$id]->getEvents() : []; + } + + } + + + /** + * @internal This class is for internal usage only + * + * Original author Fabien Potencier + */ + class Section { + /** + * @var \Symfony\Component\Stopwatch\StopwatchEvent[] + */ + private $events = []; + + /** + * @var null|float + */ + private $origin; + + /** + * @var string + */ + private $id; + + /** + * @var Section[] + */ + private $children = []; + + /** + * Constructor. + * + * @param float|null $origin + * Set the origin of the events in this section, use null to set their + * origin to their start time. + */ + public function __construct($origin = NULL) { + $this->origin = is_numeric($origin) ? $origin : NULL; + } + + /** + * Returns the child section. + * + * @param string $id + * The child section identifier. + * + * @return Section|null + * The child section or null when none found + */ + public function get($id) { + foreach ($this->children as $child) { + if ($id === $child->getId()) { + return $child; + } + } + + return NULL; + } + + /** + * Creates or re-opens a child section. + * + * @param string|null $id + * Null to create a new section, the identifier to re-open an existing + * one. + * + * @return Section + * A child section + */ + public function open($id) { + if (NULL === $session = $this->get($id)) { + $session = $this->children[] = new self(microtime(TRUE) * 1000); + } + + return $session; + } + + /** + * @return string + * The identifier of the section + */ + public function getId() { + return $this->id; + } + + /** + * Sets the session identifier. + * + * @param string $id + * The session identifier. + * + * @return Section + * The current section + */ + public function setId($id) { + $this->id = $id; + + return $this; + } + + /** + * Starts an event. + * + * @param string $name + * The event name. + * @param string $category + * The event category. + * + * @return \Symfony\Component\Stopwatch\StopwatchEvent + * The event + */ + public function startEvent($name, $category) { + if (!isset($this->events[$name])) { + $this->events[$name] = new StopwatchEvent($this->origin ?: microtime(TRUE) * 1000, $category); + } + + return $this->events[$name]->start(); + } + + /** + * Checks if an event was started. + * + * @param string $name + * The event name. + * + * @return bool + * True if the specified event was started. + */ + public function isEventStarted($name) { + return isset($this->events[$name]) && $this->events[$name]->isStarted(); + } + + /** + * Stops an event. + * + * @param string $name + * The event name. + * + * @return \Symfony\Component\Stopwatch\StopwatchEvent + * The event. + * + * @throws \LogicException + * When the event has not been started. + */ + public function stopEvent($name) { + if (!isset($this->events[$name])) { + throw new \LogicException(sprintf('Event "%s" is not started.', $name)); + } + + return $this->events[$name]->stop(); + } + + /** + * Stops then restarts an event. + * + * @param string $name + * The event name. + * + * @return \Symfony\Component\Stopwatch\StopwatchEvent + * The event. + * + * @throws \LogicException + * When the event has not been started. + */ + public function lap($name) { + return $this->stopEvent($name)->start(); + } + + /** + * Returns the events from this section. + * + * @return \Symfony\Component\Stopwatch\StopwatchEvent[] + * An array of StopwatchEvent instances + */ + public function getEvents() { + return $this->events; + } + + } + + /** + * Class StopwatchEvent. + */ + class StopwatchEvent { + /** + * @var \Symfony\Component\Stopwatch\StopwatchPeriod[] + */ + private $periods = []; + + /** + * @var float + */ + private $origin; + + /** + * @var string + */ + private $category; + + /** + * @var float[] + */ + private $started = []; + + /** + * Constructor. + * + * @param float $origin + * The origin time in milliseconds. + * @param string|null $category + * The event category or null to use the default. + * + * @throws \InvalidArgumentException + * When the raw time is not valid. + */ + public function __construct($origin, $category = NULL) { + $this->origin = $this->formatTime($origin); + $this->category = is_string($category) ? $category : 'default'; + } + + /** + * Gets the category. + * + * @return string + * The category. + */ + public function getCategory() { + return $this->category; + } + + /** + * Gets the origin. + * + * @return float + * The origin in milliseconds. + */ + public function getOrigin() { + return $this->origin; + } + + /** + * Starts a new event period. + * + * @return \Symfony\Component\Stopwatch\StopwatchEvent + * The event. + */ + public function start() { + $this->started[] = $this->getNow(); + + return $this; + } + + /** + * Stops the last started event period. + * + * @return \Symfony\Component\Stopwatch\StopwatchEvent + * The event. + * + * @throws \LogicException + * When stop() is called without a matching call to start(). + */ + public function stop() { + if (!count($this->started)) { + throw new \LogicException('stop() called but start() has not been called before.'); + } + + $this->periods[] = new StopwatchPeriod(array_pop($this->started), $this->getNow()); + + return $this; + } + + /** + * Checks if this event was started. + * + * @return bool + * True if this event was started. + */ + public function isStarted() { + return !empty($this->started); + } + + /** + * Stops the current period and then starts a new one. + * + * @return \Symfony\Component\Stopwatch\StopwatchEvent + * The event. + */ + public function lap() { + return $this->stop()->start(); + } + + /** + * Stops all non already stopped periods. + */ + public function ensureStopped() { + while (count($this->started)) { + $this->stop(); + } + } + + /** + * Gets all event periods. + * + * @return \Symfony\Component\Stopwatch\StopwatchPeriod[] + * An array of StopwatchPeriod instances. + */ + public function getPeriods() { + return $this->periods; + } + + /** + * Gets the relative time of the start of the first period. + * + * @return int + * The time (in milliseconds). + */ + public function getStartTime() { + return isset($this->periods[0]) ? $this->periods[0]->getStartTime() : 0; + } + + /** + * Gets the relative time of the end of the last period. + * + * @return int + * The time (in milliseconds). + */ + public function getEndTime() { + return ($count = count($this->periods)) ? $this->periods[$count - 1]->getEndTime() : 0; + } + + /** + * Gets the duration of the events (including all periods). + * + * @return int + * The duration (in milliseconds). + */ + public function getDuration() { + $total = 0; + foreach ($this->periods as $period) { + $total += $period->getDuration(); + } + + return $total; + } + + /** + * Gets the max memory usage of all periods. + * + * @return int + * The memory usage (in bytes). + */ + public function getMemory() { + $memory = 0; + foreach ($this->periods as $period) { + if ($period->getMemory() > $memory) { + $memory = $period->getMemory(); + } + } + + return $memory; + } + + /** + * Return the current time relative to origin. + * + * @return float + * Time in ms. + */ + protected function getNow() { + return $this->formatTime(microtime(TRUE) * 1000 - $this->origin); + } + + /** + * Formats a time. + * + * @param int|float $time + * A raw time. + * + * @return float + * The formatted time. + * + * @throws \InvalidArgumentException + * When the raw time is not valid. + */ + private function formatTime($time) { + if (!is_numeric($time)) { + throw new \InvalidArgumentException('The time must be a numerical value'); + } + + return round($time, 1); + } + + } + + /** + * Class StopwatchPeriod. + */ + class StopwatchPeriod { + private $start; + private $end; + private $memory; + + /** + * Constructor. + * + * @param int $start + * The relative time of the start of the period (in milliseconds) + * @param int $end + * The relative time of the end of the period (in milliseconds) + */ + public function __construct($start, $end) { + $this->start = (integer) $start; + $this->end = (integer) $end; + $this->memory = memory_get_usage(TRUE); + } + + /** + * Gets the relative time of the start of the period. + * + * @return int + * The time (in milliseconds). + */ + public function getStartTime() { + return $this->start; + } + + /** + * Gets the relative time of the end of the period. + * + * @return int + * The time (in milliseconds). + */ + public function getEndTime() { + return $this->end; + } + + /** + * Gets the time spent in this period. + * + * @return int + * The period duration (in milliseconds). + */ + public function getDuration() { + return $this->end - $this->start; + } + + /** + * Gets the memory usage. + * + * @return int + * The memory usage (in bytes). + */ + public function getMemory() { + return $this->memory; + } + + } + +} diff --git a/web/modules/contrib/devel/webprofiler/src/StringTranslation/TranslationManagerWrapper.php b/web/modules/contrib/devel/webprofiler/src/StringTranslation/TranslationManagerWrapper.php new file mode 100644 index 000000000..6d8431f61 --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/src/StringTranslation/TranslationManagerWrapper.php @@ -0,0 +1,71 @@ +translationManager = $translationManager; + } + + /** + * {@inheritdoc} + */ + protected function doTranslate($string, array $options = []) { + // Merge in defaults. + if (empty($options['langcode'])) { + $options['langcode'] = $this->defaultLangcode; + } + if (empty($options['context'])) { + $options['context'] = ''; + } + $translation = $this->getStringTranslation($options['langcode'], $string, $options['context']); + + if ($translation) { + $this->translated[$string] = $translation; + } + else { + $this->untranslated[$string] = $string; + } + + return $translation === FALSE ? $string : $translation; + } + + /** + * @return array + */ + public function getTranslated() { + return $this->translated; + } + + /** + * @return array + */ + public function getUntranslated() { + return $this->untranslated; + } + +} diff --git a/web/modules/contrib/devel/webprofiler/src/Theme/ThemeNegotiatorWrapper.php b/web/modules/contrib/devel/webprofiler/src/Theme/ThemeNegotiatorWrapper.php new file mode 100644 index 000000000..8c3647723 --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/src/Theme/ThemeNegotiatorWrapper.php @@ -0,0 +1,82 @@ +classResolver; + $negotiators = $this->negotiators; + } + else { + $classResolver = \Drupal::classResolver(); + $negotiators = $this->getSortedNegotiators(); + } + + foreach ($negotiators as $negotiator_id) { + if (property_exists($this, 'classResolver')) { + $negotiator = $classResolver->getInstanceFromDefinition($negotiator_id); + } + else { + $negotiator = $negotiator_id; + } + + if ($negotiator->applies($route_match)) { + $theme = $negotiator->determineActiveTheme($route_match); + if ($theme !== NULL && $this->themeAccess->checkAccess($theme)) { + $this->negotiator = $negotiator; + return $theme; + } + } + } + } + + /** + * @return \Drupal\Core\Theme\ThemeNegotiatorInterface + */ + public function getNegotiator() { + return $this->negotiator; + } + + /** + * Returns the sorted array of theme negotiators. + * + * @return array|\Drupal\Core\Theme\ThemeNegotiatorInterface[] + * An array of theme negotiator objects. + * + * @todo Remove this method when we decide to drop Drupal 8.3.x support. + */ + protected function getSortedNegotiators() { + if (!isset($this->sortedNegotiators)) { + // Sort the negotiators according to priority. + krsort($this->negotiators); + // Merge nested negotiators from $this->negotiators into + // $this->sortedNegotiators. + $this->sortedNegotiators = []; + foreach ($this->negotiators as $builders) { + $this->sortedNegotiators = array_merge($this->sortedNegotiators, $builders); + } + } + return $this->sortedNegotiators; + } + +} diff --git a/web/modules/contrib/devel/webprofiler/src/Twig/Dumper/HtmlDumper.php b/web/modules/contrib/devel/webprofiler/src/Twig/Dumper/HtmlDumper.php new file mode 100644 index 000000000..8e02796fe --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/src/Twig/Dumper/HtmlDumper.php @@ -0,0 +1,45 @@ + '#dfd', + 'macro' => '#ddf', + 'template' => '#ffd', + 'big' => '#d44', + ]; + + /** + * {@inheritdoc} + */ + public function dump(\Twig_Profiler_Profile $profile) { + return '
      ' . parent::dump($profile) . '
      '; + } + + /** + * + */ + protected function formatTemplate(\Twig_Profiler_Profile $profile, $prefix) { + return sprintf('%s└ %s', $prefix, self::$colors['template'], $profile->getTemplate()); + } + + /** + * + */ + protected function formatNonTemplate(\Twig_Profiler_Profile $profile, $prefix) { + return sprintf('%s└ %s::%s(%s)', $prefix, $profile->getTemplate(), $profile->getType(), isset(self::$colors[$profile->getType()]) ? self::$colors[$profile->getType()] : 'auto', $profile->getName()); + } + + /** + * + */ + protected function formatTime(\Twig_Profiler_Profile $profile, $percent) { + return sprintf('%.2fms/%.0f%%', $percent > 20 ? self::$colors['big'] : 'auto', $profile->getDuration() * 1000, $percent); + } + +} diff --git a/web/modules/contrib/devel/webprofiler/src/Twig/Extension/ProfilerExtension.php b/web/modules/contrib/devel/webprofiler/src/Twig/Extension/ProfilerExtension.php new file mode 100644 index 000000000..0b56b4475 --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/src/Twig/Extension/ProfilerExtension.php @@ -0,0 +1,94 @@ +ideLink = $ideLink; + $this->classShortener = $classShortener; + + $this->stopwatch = $stopwatch; + $this->events = new \SplObjectStorage(); + } + + /** + * {@inheritdoc} + */ + public function enter(\Twig_Profiler_Profile $profile) { + if ($this->stopwatch && $profile->isTemplate()) { + $this->events[$profile] = $this->stopwatch->start($profile->getName(), 'template'); + } + + parent::enter($profile); + } + + /** + * {@inheritdoc} + */ + public function leave(\Twig_Profiler_Profile $profile) { + parent::leave($profile); + + if ($this->stopwatch && $profile->isTemplate()) { + $this->events[$profile]->stop(); + unset($this->events[$profile]); + } + } + + /** + * {@inheritdoc} + */ + public function getFunctions() { + return [ + new \Twig_SimpleFunction('abbr', [$this, 'getAbbr']), + new \Twig_SimpleFunction('idelink', [$this, 'getIdeLink']), + ]; + } + + /** + * {@inheritdoc} + */ + public function getName() { + return 'native_profiler'; + } + + /** + * @param $class + * + * @return string + */ + public function getAbbr($class) { + return $this->classShortener->shortenClass($class); + } + + /** + * @param $file + * @param $line + * + * @return string + */ + public function getIdeLink($file, $line) { + return $this->ideLink->generateLink($file, $line); + } + +} diff --git a/web/modules/contrib/devel/webprofiler/src/Views/TraceableViewExecutable.php b/web/modules/contrib/devel/webprofiler/src/Views/TraceableViewExecutable.php new file mode 100644 index 000000000..5d384f71c --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/src/Views/TraceableViewExecutable.php @@ -0,0 +1,157 @@ +build_time; + } + + /** + * Gets the execute time. + * + * @return float + */ + public function getExecuteTime() { + return property_exists($this, 'execute_time') ? $this->execute_time : 0.0; + } + + /** + * Gets the render time. + * + * @return float + */ + public function getRenderTime() { + return $this->render_time; + } + + /** + * {@inheritdoc} + */ + public function render($display_id = NULL) { + $start = microtime(TRUE); + + $this->execute($display_id); + + // Check to see if the build failed. + if (!empty($this->build_info['fail'])) { + return; + } + if (!empty($this->build_info['denied'])) { + return; + } + + /** @var \Drupal\views\Plugin\views\exposed_form\ExposedFormPluginInterface $exposed_form */ + $exposed_form = $this->display_handler->getPlugin('exposed_form'); + $exposed_form->preRender($this->result); + + $module_handler = \Drupal::moduleHandler(); + + // @TODO In the longrun, it would be great to execute a view without + // the theme system at all. See https://www.drupal.org/node/2322623. + $active_theme = \Drupal::theme()->getActiveTheme(); + $themes = array_keys($active_theme->getBaseThemeExtensions()); + $themes[] = $active_theme->getName(); + + // Check for already-cached output. + /** @var \Drupal\views\Plugin\views\cache\CachePluginBase $cache */ + if (!empty($this->live_preview)) { + $cache = Views::pluginManager('cache')->createInstance('none'); + } + else { + $cache = $this->display_handler->getPlugin('cache'); + } + + // Run preRender for the pager as it might change the result. + if (!empty($this->pager)) { + $this->pager->preRender($this->result); + } + + // Initialize the style plugin. + $this->initStyle(); + + if (!isset($this->response)) { + // Set the response so other parts can alter it. + $this->response = new Response('', 200); + } + + // Give field handlers the opportunity to perform additional queries + // using the entire resultset prior to rendering. + if ($this->style_plugin->usesFields()) { + foreach ($this->field as $id => $handler) { + if (!empty($this->field[$id])) { + $this->field[$id]->preRender($this->result); + } + } + } + + $this->style_plugin->preRender($this->result); + + // Let each area handler have access to the result set. + $areas = ['header', 'footer']; + // Only call preRender() on the empty handlers if the result is empty. + if (empty($this->result)) { + $areas[] = 'empty'; + } + foreach ($areas as $area) { + foreach ($this->{$area} as $handler) { + $handler->preRender($this->result); + } + } + + // Let modules modify the view just prior to rendering it. + $module_handler->invokeAll('views_pre_render', [$this]); + + // Let the themes play too, because pre render is a very themey thing. + foreach ($themes as $theme_name) { + $function = $theme_name . '_views_pre_render'; + if (function_exists($function)) { + $function($this); + } + } + + $this->display_handler->output = $this->display_handler->render(); + + $exposed_form->postRender($this->display_handler->output); + + $cache->postRender($this->display_handler->output); + + // Let modules modify the view output after it is rendered. + $module_handler->invokeAll('views_post_render', [ + $this, + &$this->display_handler->output, + $cache, + ]); + + // Let the themes play too, because post render is a very themey thing. + foreach ($themes as $theme_name) { + $function = $theme_name . '_views_post_render'; + if (function_exists($function)) { + $function($this, $this->display_handler->output, $cache); + } + } + + $this->render_time = microtime(TRUE) - $start; + + return $this->display_handler->output; + } + +} diff --git a/web/modules/contrib/devel/webprofiler/src/Views/ViewExecutableFactoryWrapper.php b/web/modules/contrib/devel/webprofiler/src/Views/ViewExecutableFactoryWrapper.php new file mode 100644 index 000000000..3f2274399 --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/src/Views/ViewExecutableFactoryWrapper.php @@ -0,0 +1,48 @@ +views = []; + } + + /** + * {@inheritdoc} + */ + public function get(ViewEntityInterface $view) { + $view_executable = new TraceableViewExecutable($view, $this->user, $this->viewsData, $this->routeProvider); + $view_executable->setRequest($this->requestStack->getCurrentRequest()); + $this->views[] = $view_executable; + + return $view_executable; + } + + /** + * @return TraceableViewExecutable + */ + public function getViews() { + return $this->views; + } + +} diff --git a/web/modules/contrib/devel/webprofiler/src/WebprofilerServiceProvider.php b/web/modules/contrib/devel/webprofiler/src/WebprofilerServiceProvider.php new file mode 100644 index 000000000..8f7dc19e6 --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/src/WebprofilerServiceProvider.php @@ -0,0 +1,116 @@ +addCompilerPass(new ProfilerPass()); + + // Add a compiler pass to discover all available storage backend. + $container->addCompilerPass(new StoragePass()); + + $container->addCompilerPass(new ServicePass(), PassConfig::TYPE_AFTER_REMOVING); + $container->addCompilerPass(new DecoratorPass(), PassConfig::TYPE_AFTER_REMOVING); + + $modules = $container->getParameter('container.modules'); + + // Add ViewsDataCollector only if Views module is enabled. + if (isset($modules['views'])) { + $container->register('webprofiler.views', 'Drupal\webprofiler\DataCollector\ViewsDataCollector') + ->addArgument(new Reference(('views.executable'))) + ->addArgument(new Reference(('entity_type.manager'))) + ->addTag('data_collector', [ + 'template' => '@webprofiler/Collector/views.html.twig', + 'id' => 'views', + 'title' => 'Views', + 'priority' => 75, + ]); + } + + // Add BlockDataCollector only if Block module is enabled. + if (isset($modules['block'])) { + $container->register('webprofiler.blocks', 'Drupal\webprofiler\DataCollector\BlocksDataCollector') + ->addArgument(new Reference(('entity_type.manager'))) + ->addTag('data_collector', [ + 'template' => '@webprofiler/Collector/blocks.html.twig', + 'id' => 'blocks', + 'title' => 'Blocks', + 'priority' => 78, + ]); + } + + // Add TranslationsDataCollector only if Locale module is enabled. + if (isset($modules['locale'])) { + $container->register('webprofiler.translations', 'Drupal\webprofiler\DataCollector\TranslationsDataCollector') + ->addArgument(new Reference(('string_translation'))) + ->addArgument(new Reference(('url_generator'))) + ->addTag('data_collector', [ + 'template' => '@webprofiler/Collector/translations.html.twig', + 'id' => 'translations', + 'title' => 'Translations', + 'priority' => 210, + ]); + } + } + + /** + * {@inheritdoc} + */ + public function alter(ContainerBuilder $container) { + $modules = $container->getParameter('container.modules'); + + // Alter the views.executable service only if Views module is enabled. + if (isset($modules['views'])) { + $container->getDefinition('views.executable') + ->setClass('Drupal\webprofiler\Views\ViewExecutableFactoryWrapper'); + } + + // Replace the regular form_builder service with a traceable one. + $container->getDefinition('form_builder') + ->setClass('Drupal\webprofiler\Form\FormBuilderWrapper'); + + // Replace the regular access_manager service with a traceable one. + $container->getDefinition('access_manager') + ->setClass('Drupal\webprofiler\Access\AccessManagerWrapper') + ->addMethodCall('setDataCollector', [new Reference('webprofiler.request')]); + + // Replace the regular theme.negotiator service with a traceable one. + $container->getDefinition('theme.negotiator') + ->setClass('Drupal\webprofiler\Theme\ThemeNegotiatorWrapper'); + + // Replace the regular config.factory service with a traceable one. + $container->getDefinition('config.factory') + ->setClass('Drupal\webprofiler\Config\ConfigFactoryWrapper') + ->addMethodCall('setDataCollector', [new Reference('webprofiler.config')]); + + // Replace the regular string_translation service with a traceable one. + $container->getDefinition('string_translation') + ->setClass('Drupal\webprofiler\StringTranslation\TranslationManagerWrapper'); + + // Replace the regular event_dispatcher service with a traceable one. + $container->getDefinition('event_dispatcher') + ->setClass('Drupal\webprofiler\EventDispatcher\TraceableEventDispatcher') + ->addMethodCall('setStopwatch', [new Reference('stopwatch')]); + + $container->getDefinition('http_kernel.basic') + ->replaceArgument(1, new Reference('webprofiler.debug.controller_resolver')); + } + +} diff --git a/web/modules/contrib/devel/webprofiler/templates/Collector/assets.html.twig b/web/modules/contrib/devel/webprofiler/templates/Collector/assets.html.twig new file mode 100644 index 000000000..d53754140 --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/templates/Collector/assets.html.twig @@ -0,0 +1,133 @@ +{% block toolbar %} + {% set icon %} + + {{ 'Assets'|t }} + {{ collector.getcsscount + collector.getjscount }} + + {% endset %} + {% set text %} + +
      + CSS + {{ collector.getcsscount }} +
      +
      + JS + {{ collector.getjscount }} +
      + {% endset %} + +
      +
      {{ icon|default('') }}
      +
      {{ text|default('') }}
      +
      +{% endblock %} + +{% block panel %} + +{% endblock %} diff --git a/web/modules/contrib/devel/webprofiler/templates/Collector/blocks.html.twig b/web/modules/contrib/devel/webprofiler/templates/Collector/blocks.html.twig new file mode 100644 index 000000000..4146f2749 --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/templates/Collector/blocks.html.twig @@ -0,0 +1,113 @@ +{% block toolbar %} + {% set icon %} + + {{ 'Blocks'|t }} + {{ collector.renderedBlocksCount }} + + {% endset %} + {% set text %} + +
      + {{ 'Loaded'|t }} + {{ collector.loadedBlocksCount }} +
      +
      + {{ 'Rendered'|t }} + {{ collector.renderedBlocksCount }} +
      + {% endset %} + +
      +
      {{ icon|default('') }}
      +
      {{ text|default('') }}
      +
      +{% endblock %} + +{% block panel %} + +{% endblock %} diff --git a/web/modules/contrib/devel/webprofiler/templates/Collector/cache.html.twig b/web/modules/contrib/devel/webprofiler/templates/Collector/cache.html.twig new file mode 100644 index 000000000..61539b6e3 --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/templates/Collector/cache.html.twig @@ -0,0 +1,63 @@ +{% block toolbar %} + {% set icon %} + + {{ 'Cache'|t }} + {{ collector.getCacheHitsCount }} + / {{ collector.getCacheMissesCount }} + + {% endset %} + {% set text %} + + {% for bin, cids in collector.getCacheHits %} +
      + {{ bin }} + {{ cids }} +
      + {% endfor %} + {% endset %} + +
      +
      {{ icon|default('') }}
      +
      {{ text|default('') }}
      +
      +{% endblock %} + +{% block panel %} + +{% endblock %} diff --git a/web/modules/contrib/devel/webprofiler/templates/Collector/config.html.twig b/web/modules/contrib/devel/webprofiler/templates/Collector/config.html.twig new file mode 100644 index 000000000..954ea5f12 --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/templates/Collector/config.html.twig @@ -0,0 +1,37 @@ +{% block toolbar %} + {% set icon %} + + {{ 'Config'|t }} + {{ collector.getConfigNames|length }} + + {% endset %} + {% set text %} +
      + {{ 'Config get'|t }} + {{ collector.getConfigNames|length }} +
      + {% endset %} + +
      +
      {{ icon|default('') }}
      +
      {{ text|default('') }}
      +
      +{% endblock %} + +{% block panel %} + +{% endblock %} diff --git a/web/modules/contrib/devel/webprofiler/templates/Collector/database.html.twig b/web/modules/contrib/devel/webprofiler/templates/Collector/database.html.twig new file mode 100644 index 000000000..f07b7f1fd --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/templates/Collector/database.html.twig @@ -0,0 +1,168 @@ +{% block toolbar %} + {% set icon %} + + {{ 'Database'|t }} + {{ collector.querycount }} + {% if collector.querycount > 0 %} + in {{ '%0.2f ms'|format(collector.time) }} + {% endif %} + + {% endset %} + {% set text %} +
      + {{ 'DB Queries'|t }} + {{ collector.querycount }} +
      +
      + {{ 'Query time'|t }} + {{ '%0.2f ms'|format(collector.time) }} +
      +
      + {{ 'Default database'|t }} + {{ collector.database.driver }}://{{ collector.database.host }}:{{ collector.database.port }} + /{{ collector.database.database }} +
      + {% endset %} + +
      +
      {{ icon|default('') }}
      +
      {{ text|default('') }}
      +
      +{% endblock %} + +{% block panel %} + + +{% endblock %} diff --git a/web/modules/contrib/devel/webprofiler/templates/Collector/devel.html.twig b/web/modules/contrib/devel/webprofiler/templates/Collector/devel.html.twig new file mode 100644 index 000000000..7b6ba3a24 --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/templates/Collector/devel.html.twig @@ -0,0 +1,24 @@ +{% block toolbar %} + {% set icon %} + + {{ collector.title }} + + {% endset %} + + {% set text %} + {% spaceless %} + {% for item in collector.links %} +
      + {{ item }} +
      + {% endfor %} + {% endspaceless %} + {% endset %} + +
      +
      {{ icon|default('') }}
      +
      {{ text|default('') }}
      +
      + +{% endblock %} diff --git a/web/modules/contrib/devel/webprofiler/templates/Collector/drupal.html.twig b/web/modules/contrib/devel/webprofiler/templates/Collector/drupal.html.twig new file mode 100644 index 000000000..1c1dc48bc --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/templates/Collector/drupal.html.twig @@ -0,0 +1,66 @@ +{% block toolbar %} + {% set icon %} + + Drupal + + {{ collector.version }} + + + {% endset %} + {% set text %} + {% spaceless %} +
      + {{ 'Drupal version'|t }} + {{ collector.version }} +
      +
      + {{ 'Installed profile'|t }} + {{ collector.profile }} +
      + + {% if collector.getGitCommit %} +
      + {{ 'Git commit'|t }} + {{ collector.getAbbrGitCommit }} +
      + {% endif %} + + + +
      + + + {% endspaceless %} + {% endset %} + + {% if link %} + {% set icon %} + {{ icon }} + {% endset %} + {% endif %} +
      +
      {{ icon|default('') }}
      +
      {{ text|default('') }}
      +
      +{% endblock %} + +{% block panel %} + +{% endblock %} + diff --git a/web/modules/contrib/devel/webprofiler/templates/Collector/events.html.twig b/web/modules/contrib/devel/webprofiler/templates/Collector/events.html.twig new file mode 100644 index 000000000..2794fec98 --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/templates/Collector/events.html.twig @@ -0,0 +1,88 @@ +{% block toolbar %} + {% set icon %} + + {{ 'Events'|t }} + {{ collector.getCalledListenersCount }} + + {% endset %} + + {% set text %} +
      + {{ 'Called'|t }} + {{ collector.getCalledListenersCount }} +
      + +
      + {{ 'Not called'|t }} + {{ collector.getNotCalledListenersCount }} +
      + {% endset %} + +
      +
      {{ icon|default('') }}
      +
      {{ text|default('') }}
      +
      +{% endblock %} + +{% block panel %} + +{% endblock %} diff --git a/web/modules/contrib/devel/webprofiler/templates/Collector/extensions.html.twig b/web/modules/contrib/devel/webprofiler/templates/Collector/extensions.html.twig new file mode 100644 index 000000000..987a9feeb --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/templates/Collector/extensions.html.twig @@ -0,0 +1,95 @@ +{% block toolbar %} + {% set icon %} + + {{ 'Extensions'|t }} + {{ collector.getExtensionsCount }} + + {% endset %} + {% set text %} +
      + {{ 'Active Modules'|t }} + {{ collector.getModulesCount }} +
      +
      + {{ 'Active Themes'|t }} + {{ collector.getThemesCount }} +
      + {% endset %} + +
      +
      {{ icon|default('') }}
      +
      {{ text|default('') }}
      +
      +{% endblock %} + +{% block panel %} + +{% endblock %} diff --git a/web/modules/contrib/devel/webprofiler/templates/Collector/forms.html.twig b/web/modules/contrib/devel/webprofiler/templates/Collector/forms.html.twig new file mode 100644 index 000000000..a602f4558 --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/templates/Collector/forms.html.twig @@ -0,0 +1,67 @@ +{% block toolbar %} + {% set icon %} + + {{ 'Forms'|t }} + {{ collector.getFormsCount }} + + {% endset %} + {% set text %} + {% spaceless %} +
      + {% for keys, form in collector.getForms %} + {{ keys }} + + {% endfor %} +
      + {% endspaceless %} + {% endset %} + +
      +
      {{ icon|default('') }}
      +
      {{ text|default('') }}
      +
      +{% endblock %} + +{% block panel %} + +{% endblock %} diff --git a/web/modules/contrib/devel/webprofiler/templates/Collector/http.html.twig b/web/modules/contrib/devel/webprofiler/templates/Collector/http.html.twig new file mode 100644 index 000000000..ea7ad0496 --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/templates/Collector/http.html.twig @@ -0,0 +1,77 @@ +{% block toolbar %} + {% set icon %} + + {{ 'Http'|t }} + {{ collector.completedRequestsCount }} + + {% endset %} + {% set text %} +
      + {{ 'Completed'|t }} + {{ collector.completedRequestsCount }} +
      +
      + {{ 'Failed'|t }} + {{ collector.failedRequestsCount }} +
      + {% endset %} + +
      +
      {{ icon|default('') }}
      +
      {{ text|default('') }}
      +
      +{% endblock %} + +{% block panel %} + +{% endblock %} diff --git a/web/modules/contrib/devel/webprofiler/templates/Collector/mail.html.twig b/web/modules/contrib/devel/webprofiler/templates/Collector/mail.html.twig new file mode 100644 index 000000000..3b34062b6 --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/templates/Collector/mail.html.twig @@ -0,0 +1,76 @@ +{% block toolbar %} + {% set icon %} + + {{ 'Mail'|t }} + {{ collector.mailsent }} + + {% endset %} + {% set text %} +
      + Mail sent + {{ collector.mailsent }} +
      + {% endset %} + +
      +
      {{ icon|default('') }}
      +
      {{ text|default('') }}
      +
      +{% endblock %} + +{% block panel %} + +{% endblock %} diff --git a/web/modules/contrib/devel/webprofiler/templates/Collector/performance_timing.html.twig b/web/modules/contrib/devel/webprofiler/templates/Collector/performance_timing.html.twig new file mode 100644 index 000000000..5e8cf41c9 --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/templates/Collector/performance_timing.html.twig @@ -0,0 +1,76 @@ +{% block toolbar %} + {% set icon %} + + {{ 'Performance Timing'|t }} + + + {% endset %} + {% set text %} + +
      + {{ 'DNS lookup'|t }} + +
      +
      + {{ 'TCP handshake'|t }} + +
      +
      + {{ 'TTFB' }} + +
      +
      + {{ 'Data download'|t }} + +
      +
      + {{ 'DOM building'|t }} + +
      + {% endset %} + +
      +
      {{ icon|default('') }}
      +
      {{ text|default('') }}
      +
      + + +{% endblock %} + +{% block panel %} + +{% endblock %} \ No newline at end of file diff --git a/web/modules/contrib/devel/webprofiler/templates/Collector/php_config.html.twig b/web/modules/contrib/devel/webprofiler/templates/Collector/php_config.html.twig new file mode 100644 index 000000000..c91f9f321 --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/templates/Collector/php_config.html.twig @@ -0,0 +1,52 @@ +{% block toolbar %} + {# PHP Information #} + {% set icon %} + + {{ 'PHP config'|t }} + + {% endset %} + {% set text %} + {% spaceless %} + +
      + {{ 'PHP Extensions'|t }} + xdebug + accel +
      +
      + {{ 'PHP SAPI'|t }} + {{ collector.sapiName }} +
      +
      + Token + {{ token }} +
      + {% endspaceless %} + {% endset %} + +
      +
      {{ icon|default('') }}
      +
      {{ text|default('') }}
      +
      +{% endblock %} + +{% block panel %} + +{% endblock %} diff --git a/web/modules/contrib/devel/webprofiler/templates/Collector/request.html.twig b/web/modules/contrib/devel/webprofiler/templates/Collector/request.html.twig new file mode 100644 index 000000000..b57a85419 --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/templates/Collector/request.html.twig @@ -0,0 +1,216 @@ +{% block toolbar %} + {% set request_handler %} + {{ abbr(collector.controller.class)|raw }} + ::{{ collector.controller.method }} + {% endset %} + + {% set request_status_code_color = (400 > collector.statuscode) ? ((200 == collector.statuscode) ? 'green' : 'yellow') : 'red' %} + + {% set request_route = collector.route ? collector.route : 'NONE' %} + + {% set icon %} + + {{ 'Request'|t }} + {{ collector.statuscode }} + + {% endset %} + + {% set text %} + {% spaceless %} +
      + {{ 'Status'|t }} + {{ collector.statuscode }} {{ collector.statustext }} +
      +
      + {{ 'Controller'|t }} + {{ request_handler }} +
      +
      + {{ 'Route name'|t }} + {{ request_route }} +
      + {% endspaceless %} + {% endset %} + +
      +
      {{ icon|default('') }}
      +
      {{ text|default('') }}
      +
      +{% endblock %} + +{% block panel %} + +{% endblock %} diff --git a/web/modules/contrib/devel/webprofiler/templates/Collector/routing.html.twig b/web/modules/contrib/devel/webprofiler/templates/Collector/routing.html.twig new file mode 100644 index 000000000..76700112a --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/templates/Collector/routing.html.twig @@ -0,0 +1,50 @@ +{% block toolbar %} + {% set icon %} + + {{ 'Routing'|t }} + {{ collector.getRoutesCount }} + + + {% endset %} + + {% set text %} +
      + {{ 'Routes'|t }} + {{ collector.getRoutesCount }} +
      + {% endset %} + +
      +
      {{ icon|default('') }}
      +
      {{ text|default('') }}
      +
      +{% endblock %} + +{% block panel %} + +{% endblock %} diff --git a/web/modules/contrib/devel/webprofiler/templates/Collector/services.html.twig b/web/modules/contrib/devel/webprofiler/templates/Collector/services.html.twig new file mode 100644 index 000000000..dab3e55dd --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/templates/Collector/services.html.twig @@ -0,0 +1,149 @@ +{% block toolbar %} + {% set icon %} + + {{ 'Services'|t }} + {{ collector.getInitializedServicesCount }} + + {% endset %} + {% set text %} +
      + {{ 'Initialized'|t }} + {{ collector.getInitializedServicesCount }} ({{ collector.getInitializedServicesWithoutWebprofilerCount }} + ) +
      +
      + {{ 'Available'|t }} + {{ collector.getServicesCount }} +
      + {% endset %} + +
      +
      {{ icon|default('') }}
      +
      {{ text|default('') }}
      +
      +{% endblock %} + +{% block panel %} + +{% endblock %} diff --git a/web/modules/contrib/devel/webprofiler/templates/Collector/state.html.twig b/web/modules/contrib/devel/webprofiler/templates/Collector/state.html.twig new file mode 100644 index 000000000..e19b13baf --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/templates/Collector/state.html.twig @@ -0,0 +1,36 @@ +{% block toolbar %} + {% set icon %} + + {{ 'State'|t }} + {{ collector.getStateKeysCount }} + + {% endset %} + {% set text %} +
      + {{ 'State get'|t }} + {{ collector.getStateKeysCount }} +
      + {% endset %} + +
      +
      {{ icon|default('') }}
      +
      {{ text|default('') }}
      +
      +{% endblock %} + +{% block panel %} + +{% endblock %} diff --git a/web/modules/contrib/devel/webprofiler/templates/Collector/theme.html.twig b/web/modules/contrib/devel/webprofiler/templates/Collector/theme.html.twig new file mode 100644 index 000000000..66fa5fdfe --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/templates/Collector/theme.html.twig @@ -0,0 +1,147 @@ +{% block toolbar %} + {% set negotiator %} + {{ abbr(collector.themeNegotiator.class.class)|raw }} + ::{{ collector.themeNegotiator.class.method }} + {% endset %} + {% set time = collector.templatecount ? '%0.0f ms'|format(collector.time) : 'n/a' %} + {% set icon %} + + {{ 'Theme'|t }} + {{ collector.activeTheme.name }} + + {% endset %} + {% set text %} + +
      + {{ 'Name'|t }} + {{ collector.activeTheme.name }} +
      +
      + {{ 'Engine'|t }} + {{ collector.activeTheme.engine }} +
      +
      + {{ 'Negotiator'|t }} + {{ negotiator }} +
      +
      + {{ 'Render Time'|t }} + {{ time }} +
      +
      + {{ 'Template Calls'|t }} + {{ collector.templatecount }} +
      +
      + {{ 'Block Calls'|t }} + {{ collector.blockcount }} +
      +
      + {{ 'Macro Calls'|t }} + {{ collector.macrocount }} +
      + {% endset %} + +
      +
      {{ icon|default('') }}
      +
      {{ text|default('') }}
      +
      +{% endblock %} + +{% block panel %} + +{% endblock %} diff --git a/web/modules/contrib/devel/webprofiler/templates/Collector/time.html.twig b/web/modules/contrib/devel/webprofiler/templates/Collector/time.html.twig new file mode 100644 index 000000000..9aeb76a37 --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/templates/Collector/time.html.twig @@ -0,0 +1,59 @@ +{% block toolbar %} + {% set duration = collector.events|length ? '%.0f ms'|format(collector.duration) : 'n/a' %} + {% set memory = '%.1f MB'|format(collector.memory / 1024 / 1024) %} + {% set total_memory = collector.memoryLimit == -1 ? '∞' : '%.1f MB'|format(collector.memoryLimit / 1024)|escape %} + {% set icon %} + + {{ 'Timeline'|t }} + {{ duration }} / {{ memory }} + + {% endset %} + {% set text %} +
      + {{ 'Total time'|t }} + {{ duration }} +
      +
      + {{ 'Memory'|t }} + {{ memory }} / {{ total_memory }} +
      + {% endset %} + +
      +
      {{ icon|default('') }}
      +
      {{ text|default('') }}
      +
      +{% endblock %} + +{% block panel %} + +{% endblock %} diff --git a/web/modules/contrib/devel/webprofiler/templates/Collector/translations.html.twig b/web/modules/contrib/devel/webprofiler/templates/Collector/translations.html.twig new file mode 100644 index 000000000..08683ba91 --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/templates/Collector/translations.html.twig @@ -0,0 +1,85 @@ +{% block toolbar %} + {% set icon %} + + {{ 'Translations'|t }} + {{ collector.untranslatedCount }} + + {% endset %} + {% set text %} +
      + {{ 'Translated'|t }} + {{ collector.translatedCount }} +
      +
      + {{ 'Untranslated'|t }} + {{ collector.untranslatedCount }} +
      + {% endset %} + +
      +
      {{ icon|default('') }}
      +
      {{ text|default('') }}
      +
      +{% endblock %} + +{% block panel %} + +{% endblock %} diff --git a/web/modules/contrib/devel/webprofiler/templates/Collector/user.html.twig b/web/modules/contrib/devel/webprofiler/templates/Collector/user.html.twig new file mode 100644 index 000000000..ef369975e --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/templates/Collector/user.html.twig @@ -0,0 +1,63 @@ +{% block toolbar %} + {% set color_code = (collector.authenticated) ? 'green' : 'red' %} + {% set icon %} + + {{ 'User'|t }} + + {% if collector.authenticated %} + {{ collector.username }} + {% endif %} + + + {% endset %} + {% set text %} + {% if collector.authenticated %} +
      + {{ 'Logged in as'|t }} + {{ collector.username }} +
      +
      + {{ 'Roles'|t }} + {{ collector.roles|join(', ') }} +
      +
      + {{ 'Authenticated by'|t }} + {{ collector.provider }} +
      + {% else %} + {{ collector.anonymous }} + {% endif %} + {% endset %} + +
      +
      {{ icon|default('') }}
      +
      {{ text|default('') }}
      +
      +{% endblock %} + +{% block panel %} + +{% endblock %} diff --git a/web/modules/contrib/devel/webprofiler/templates/Collector/views.html.twig b/web/modules/contrib/devel/webprofiler/templates/Collector/views.html.twig new file mode 100644 index 000000000..5409cc9b5 --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/templates/Collector/views.html.twig @@ -0,0 +1,58 @@ +{% block toolbar %} + {% set icon %} + + {{ 'Views'|t }} + {{ collector.getViewsCount }} + + {% endset %} + {% set text %} + +
      + {{ 'Views'|t }} + {{ collector.getViewsCount }} +
      + {% endset %} + +
      +
      {{ icon|default('') }}
      +
      {{ text|default('') }}
      +
      +{% endblock %} + +{% block panel %} + +{% endblock %} diff --git a/web/modules/contrib/devel/webprofiler/templates/Profiler/webprofiler_dashboard.html.twig b/web/modules/contrib/devel/webprofiler/templates/Profiler/webprofiler_dashboard.html.twig new file mode 100644 index 000000000..be4fb68c9 --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/templates/Profiler/webprofiler_dashboard.html.twig @@ -0,0 +1,57 @@ +{% set resume %} +
      + + {{ profile.method|upper }} + {% if profile.method|upper in ['GET', 'HEAD'] %} + {{ profile.url }} + {% else %} + {{ profile.url }} + {% endif %} + + + by {{ profile.ip }} at {{ profile.time|date('r') }} + + + {{ 'View latest'|t }} +
      +{% endset %} + +
      + + {{ resume }} + +
      +
      + + + + + + {{ panels }} + + +
      diff --git a/web/modules/contrib/devel/webprofiler/templates/Profiler/webprofiler_loader.html.twig b/web/modules/contrib/devel/webprofiler/templates/Profiler/webprofiler_loader.html.twig new file mode 100644 index 000000000..ad8da9bd0 --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/templates/Profiler/webprofiler_loader.html.twig @@ -0,0 +1,73 @@ + + + diff --git a/web/modules/contrib/devel/webprofiler/templates/Profiler/webprofiler_panel.html.twig b/web/modules/contrib/devel/webprofiler/templates/Profiler/webprofiler_panel.html.twig new file mode 100644 index 000000000..b06daff3f --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/templates/Profiler/webprofiler_panel.html.twig @@ -0,0 +1 @@ +{{ panel|raw }} diff --git a/web/modules/contrib/devel/webprofiler/templates/Profiler/webprofiler_toolbar.css.twig b/web/modules/contrib/devel/webprofiler/templates/Profiler/webprofiler_toolbar.css.twig new file mode 100644 index 000000000..fccb64b0f --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/templates/Profiler/webprofiler_toolbar.css.twig @@ -0,0 +1,311 @@ +.sf-minitoolbar { +display: none; + +position: fixed; +bottom: 0; +right: 0; + +padding: 5px 5px 0; + +background-color: #f7f7f7; +background-image: -moz-linear-gradient(top, #e4e4e4, #ffffff); +background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, from(#e4e4e4), to(#ffffff)); +background-image: -o-linear-gradient(top, #e4e4e4, #ffffff); +background: linear-gradient(top, #e4e4e4, #ffffff); + +border-radius: 16px 0 0 0; + +z-index: 6000000; +} + +.sf-toolbarreset { +position: fixed; +background-color: #f7f7f7; +left: 0; +right: 0; +height: 38px; +margin: 0; +padding: 0 40px 0 0; +z-index: 6000000; +font: 11px Verdana, Arial, sans-serif; +text-align: left; +color: #2f2f2f; + +background-image: -moz-linear-gradient(top, #e4e4e4, #ffffff); +background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, from(#e4e4e4), to(#ffffff)); +background-image: -o-linear-gradient(top, #e4e4e4, #ffffff); +background: linear-gradient(top, #e4e4e4, #ffffff); + +bottom: 0; +border-top: 1px solid #bbb; +} +.sf-toolbarreset abbr { +border-bottom: 1px dotted #000000; +} +.sf-toolbarreset span, +.sf-toolbarreset div { +font-size: 11px; +} +.sf-toolbarreset img { +width: auto; +display: inline; +} + +.sf-toolbarreset .hide-button { +display: block; +position: absolute; +top: 0; +right: 0; +width: 40px; +height: 40px; +cursor: pointer; +background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA8AAAAPCAMAAAAMCGV4AAAAllBMVEXDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PExMTPz8/Q0NDR0dHT09Pb29vc3Nzf39/h4eHi4uLj4+P6+vr7+/v8/Pz9/f3///+Nh2QuAAAAIXRSTlMABgwPGBswMzk8QktRV4SKjZOWmaKlq7TAxszb3urt+fy1vNEpAAAAiklEQVQIHUXBBxKCQBREwRFzDqjoGh+C2YV//8u5Sll2S0E/dof1tKdKM6GyqCto7PjZRJIS/mbSELgXOSd/BzpKIH1ZefVWpDDTHsi8mZVnwImPi5ndCLbaAZk3M58Bay0h9VbeSvMpjDUAHj4jL55AW1rxN5fU2PLjIgVRzNdxVFOlGzvnJi0Fb1XNGBHA9uQOAAAAAElFTkSuQmCC'); +background-repeat: no-repeat; +background-position: 13px 11px; +} +.sf-toolbarreset a { +color: #000 !important; +border: none; +} + +.sf-toolbar-block { +white-space: nowrap; +color: #2f2f2f; +display: block; +min-height: 28px; +border-right: 1px solid #e4e4e4; +padding: 0; +float: left; +cursor: default; +} + +.sf-toolbar-block span { +display: inline-block; +} + +.sf-toolbar-block .sf-toolbar-info-piece { +line-height: 19px; +margin-bottom: 5px; +} + +.sf-toolbar-block .sf-toolbar-info-piece .sf-toolbar-status { +padding: 0px 5px; +border-radius: 5px; +margin-bottom: 0px; +vertical-align: top; +} + +.sf-toolbar-block .sf-toolbar-info-piece:last-child { +margin-bottom: 0; +} + +.sf-toolbar-block .sf-toolbar-info-piece a, +.sf-toolbar-block .sf-toolbar-info-piece abbr { +color: #2f2f2f; +} + +.sf-toolbar-block .sf-toolbar-info-piece b { +display: inline-block; +width: 150px; +vertical-align: top; +} + +.sf-toolbar-block .sf-toolbar-info-with-next-pointer:after { +content: ' :: '; +color: #999; +} + +.sf-toolbar-block .sf-toolbar-info-with-delimiter { +border-right: 1px solid #999; +padding-right: 5px; +margin-right: 5px; +} + +.sf-toolbar-block .sf-toolbar-info { +display: none; +position: absolute; +background-color: #fff; +border: 1px solid #bbb; +padding: 9px 0; +margin-left: -1px; + +bottom: 38px; +border-bottom-width: 0; +border-bottom: 1px solid #bbb; +border-radius: 4px 4px 0 0; +} + +.sf-toolbarreset > div:last-of-type .sf-toolbar-info { +right: -1px; +} + +.sf-toolbar-block .sf-toolbar-info:empty { +visibility: hidden; +} + +.sf-toolbar-block .sf-toolbar-status { +display: inline-block; +color: #fff; +background-color: #666; +padding: 3px 6px; +border-radius: 3px; +margin-bottom: 2px; +vertical-align: middle; +min-width: 6px; +min-height: 13px; +} + +.sf-toolbar-block .sf-toolbar-status abbr { +color: #fff; +border-bottom: 1px dotted black; +} + +.sf-toolbar-block .sf-toolbar-status-green { +background-color: #759e1a; +} + +.sf-toolbar-block .sf-toolbar-status-red { +background-color: #a33; +} + +.sf-toolbar-block .sf-toolbar-status-yellow { +background-color: #ffcc00; +color: #000; +} + +.sf-toolbar-block .sf-toolbar-status-black { +background-color: #000; +} + +.sf-toolbar-block .sf-toolbar-icon { +display: block; +} + +.sf-toolbar-block .sf-toolbar-icon > a, +.sf-toolbar-block .sf-toolbar-icon > span { +display: block; +font-weight: normal; +text-decoration: none; +margin: 0; +padding: 5px 6px; +min-width: 20px; +text-align: center; +vertical-align: middle; +} + +.sf-toolbar-block .sf-toolbar-icon > a, +.sf-toolbar-block .sf-toolbar-icon > a:link +.sf-toolbar-block .sf-toolbar-icon > a:hover { +color: black !important; +} + +.sf-toolbar-block .sf-toolbar-icon > a[href]:after { +content: ""; +} + +.sf-toolbar-block .sf-toolbar-icon img { +border-width: 0; +vertical-align: middle; +} + +.sf-toolbar-block .sf-toolbar-icon img + span { +margin-left: 0; +margin-top: 2px; +} + +.sf-toolbar-block .sf-toolbar-icon .sf-toolbar-status { +border-radius: 12px; +border-bottom-left-radius: 0; +margin-top: 0; +} + +.sf-toolbar-block .sf-toolbar-info-method { +cursor: hand; +} + +.sf-toolbar-block .sf-toolbar-info-method[onclick=""] { +border-bottom: none; +cursor: inherit; +} + +.sf-toolbar-info-php {} +.sf-toolbar-info-php-ext {} + +.sf-toolbar-info-php-ext .sf-toolbar-status { +margin-left: 2px; +} + +.sf-toolbar-info-php-ext .sf-toolbar-status:first-child { +margin-left: 0; +} + +.sf-toolbar-block a .sf-toolbar-info-piece-additional, +.sf-toolbar-block a .sf-toolbar-info-piece-additional-detail { +display: inline-block; +} + +.sf-toolbar-block a .sf-toolbar-info-piece-additional:empty, +.sf-toolbar-block a .sf-toolbar-info-piece-additional-detail:empty { +display: none; +} + +.sf-toolbarreset:hover { +box-shadow: rgba(0, 0, 0, 0.3) 0 0 50px; +} + +.sf-toolbar-block:hover { +box-shadow: rgba(0, 0, 0, 0.35) 0 0 5px; +border-right: none; +margin-right: 1px; +position: relative; +} + +.sf-toolbar-block:hover .sf-toolbar-icon { +background-color: #fff; +border-top: 1px dotted #DDD; +position: relative; +margin-top: -1px; +z-index: 10002; +} + +.sf-toolbar-block:hover .sf-toolbar-info { +display: block; +min-width: -webkit-calc(100% + 2px); +min-width: calc(100% + 2px); +z-index: 10001; +box-sizing: border-box; +padding: 9px; +line-height: 19px; +} + +/***** Media query *****/ +@media screen and (max-width: 779px) { +.sf-toolbar-block .sf-toolbar-icon > * > :first-child ~ * { +display: none; +} + +.sf-toolbar-block .sf-toolbar-icon > * > .sf-toolbar-info-piece-additional, +.sf-toolbar-block .sf-toolbar-icon > * > .sf-toolbar-info-piece-additional-detail { +display: none !important; +} +} + +@media screen and (max-width: 1179px) { +.sf-toolbar-block .sf-toolbar-icon > * > .sf-toolbar-info-piece-additional { +display: none; +} +} + +@media screen and (max-width: 1439px) { +.sf-toolbar-block .sf-toolbar-icon > * > .sf-toolbar-info-piece-additional-detail { +display: none; +} +} + +/***** Media query print: Do not print the Toolbar. *****/ +@media print { +.sf-toolbar { +display: none; +visibility: hidden; +} +} diff --git a/web/modules/contrib/devel/webprofiler/templates/Profiler/webprofiler_toolbar.html.twig b/web/modules/contrib/devel/webprofiler/templates/Profiler/webprofiler_toolbar.html.twig new file mode 100644 index 000000000..f452aec02 --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/templates/Profiler/webprofiler_toolbar.html.twig @@ -0,0 +1,35 @@ + + +
      + +
      + {{ toolbar|raw }} + + +
      diff --git a/web/modules/contrib/devel/webprofiler/tests/src/Functional/DataCollectorTest.php b/web/modules/contrib/devel/webprofiler/tests/src/Functional/DataCollectorTest.php new file mode 100644 index 000000000..60e9884b4 --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/tests/src/Functional/DataCollectorTest.php @@ -0,0 +1,68 @@ +container->get('entity_type.manager'); + $this->blocksDataCollector = new BlocksDataCollector($entity_type_manager); + } + + /** + * Tests the Blocks data collector. + */ + public function testBlocksDataCollector() { + // This test was added to ensure we do not have any regression faults such + // as happened with the blocksDataCollector. + // @see https://www.drupal.org/project/devel/issues/3106747 + $this->drupalPlaceBlock('page_title_block', ['id' => 'page-title']); + $this->drupalPlaceBlock('local_tasks_block', ['id' => 'local-tasks']); + + $this->drupalLogin($this->rootUser); + + // What is the right way to collect the block data? The blocks are being + // placed OK above but the following does not work (obviously). + // $data has a 'blocks' key and that array has 'loaded' and 'rendered' keys + // but the values are empty. + $request = Request::create('', 'GET'); + $response = Response::create(); + $this->blocksDataCollector->collect($request, $response); + $data = $this->blocksDataCollector->getData(); + $this->assertCount(1, $data); + } + +} diff --git a/web/modules/contrib/devel/webprofiler/tests/src/FunctionalJavascript/ToolbarTest.php b/web/modules/contrib/devel/webprofiler/tests/src/FunctionalJavascript/ToolbarTest.php new file mode 100644 index 000000000..5daa8359c --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/tests/src/FunctionalJavascript/ToolbarTest.php @@ -0,0 +1,74 @@ +getEditable('system.site')->set('page.front', '/node')->save(TRUE); + } + + /** + * Tests if the toolbar appears on front page. + */ + public function testToolbarOnFrontPage() { + $this->loginForToolbar(); + + $this->drupalGet(''); + + $this->waitForToolbar(); + + $assert = $this->assertSession(); + $assert->responseContains('Configure Webprofiler'); + $assert->responseContains('View latest reports'); + $assert->responseContains('Drupal Documentation'); + $assert->responseContains('Get involved!'); + } + + /** + * Tests the toolbar report page. + */ + public function testToolbarReportPage() { + $this->loginForDashboard(); + + $this->drupalGet(''); + + $this->drupalGet('admin/reports/profiler/list'); + + // @todo assert some content. + } + + /** + * Tests the toolbar not appears on excluded path. + */ + public function testToolbarNotAppearsOnExcludedPath() { + $this->loginForDashboard(); + + $this->drupalGet('admin/config/development/devel'); + $this->waitForToolbar(); + $assert = $this->assertSession(); + $assert->responseContains('Configure Webprofiler'); + + $this->config('webprofiler.config') + ->set('exclude', '/admin/config/development/devel') + ->save(); + $this->drupalGet('admin/config/development/devel'); + $this->assertSession()->responseNotContains('sf-toolbar'); + } + +} diff --git a/web/modules/contrib/devel/webprofiler/tests/src/FunctionalJavascript/WebprofilerTestBase.php b/web/modules/contrib/devel/webprofiler/tests/src/FunctionalJavascript/WebprofilerTestBase.php new file mode 100644 index 000000000..4f473349b --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/tests/src/FunctionalJavascript/WebprofilerTestBase.php @@ -0,0 +1,60 @@ +assertSession(); + $assert_session->waitForText(\Drupal::VERSION); + } + + /** + * Login with a user that can see the toolbar. + */ + protected function loginForToolbar() { + $admin_user = $this->drupalCreateUser( + [ + 'view webprofiler toolbar', + ] + ); + $this->drupalLogin($admin_user); + } + + /** + * Login with a user that can see the toolbar and the dashboard. + */ + protected function loginForDashboard() { + $admin_user = $this->drupalCreateUser( + [ + 'view webprofiler toolbar', + 'access webprofiler', + ] + ); + $this->drupalLogin($admin_user); + } + + /** + * Flush cache. + */ + protected function flushCache() { + $module_handler = \Drupal::moduleHandler(); + $module_handler->invokeAll('cache_flush'); + } + +} diff --git a/web/modules/contrib/devel/webprofiler/tests/src/Kernel/DecoratorTest.php b/web/modules/contrib/devel/webprofiler/tests/src/Kernel/DecoratorTest.php new file mode 100644 index 000000000..701e0af7a --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/tests/src/Kernel/DecoratorTest.php @@ -0,0 +1,65 @@ +container->get($service); + + $this->assertInstanceOf($original, $entityTypeManagerOriginal); + + $this->container->get('module_installer')->install(['webprofiler']); + + $entityTypeManagerDecorated = $this->container->get($service); + + $this->assertInstanceOf($decorated, $entityTypeManagerDecorated); + } + + /** + * DataProvider for testEntityTypeDecorator. + * + * @return array + * The array of values to run tests on. + */ + public function decorators() { + return [ + ['entity_type.manager', 'Drupal\Core\Entity\EntityTypeManager', 'Drupal\webprofiler\Entity\EntityManagerWrapper'], + ['cache_factory', 'Drupal\Core\Cache\MemoryBackendFactory', 'Drupal\webprofiler\Cache\CacheFactoryWrapper'], + ['asset.css.collection_renderer', 'Drupal\Core\Asset\CssCollectionRenderer', 'Drupal\webprofiler\Asset\CssCollectionRendererWrapper'], + ['asset.js.collection_renderer', 'Drupal\Core\Asset\JsCollectionRenderer', 'Drupal\webprofiler\Asset\JsCollectionRendererWrapper'], + ['state', 'Drupal\Core\State\State', 'Drupal\webprofiler\State\StateWrapper'], + ['views.executable', 'Drupal\views\ViewExecutableFactory', 'Drupal\webprofiler\Views\ViewExecutableFactoryWrapper'], + ['form_builder', 'Drupal\Core\Form\FormBuilder', 'Drupal\webprofiler\Form\FormBuilderWrapper'], + ['access_manager', 'Drupal\Core\Access\AccessManager', 'Drupal\webprofiler\Access\AccessManagerWrapper'], + ['theme.negotiator', 'Drupal\Core\Theme\ThemeNegotiator', 'Drupal\webprofiler\Theme\ThemeNegotiatorWrapper'], + ['config.factory', 'Drupal\Core\Config\ConfigFactory', 'Drupal\webprofiler\Config\ConfigFactoryWrapper'], + ['string_translation', 'Drupal\Core\StringTranslation\TranslationManager', 'Drupal\webprofiler\StringTranslation\TranslationManagerWrapper'], + ]; + } + +} diff --git a/web/modules/contrib/devel/webprofiler/tests/src/Unit/DataCollector/AssetsDataCollectorTest.php b/web/modules/contrib/devel/webprofiler/tests/src/Unit/DataCollector/AssetsDataCollectorTest.php new file mode 100644 index 000000000..1c129813e --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/tests/src/Unit/DataCollector/AssetsDataCollectorTest.php @@ -0,0 +1,68 @@ +assetDataCollector = new AssetsDataCollector(AssetsDataCollectorTest::ROOT); + $this->assetCollectionRendererInterface = $this->createMock('Drupal\Core\Asset\AssetCollectionRendererInterface'); + } + + /** + * Tests the Assets data collector. + */ + public function testCSS() { + $css = [ + 'core/assets/vendor/normalize-css/normalize.css' => [ + 'weight' => -219.944, + 'group' => 0, + 'type' => 'file', + 'data' => 'core\/assets\/vendor\/normalize-css\/normalize.css', + 'version' => '3.0.3', + 'media' => 'all', + 'preprocess' => TRUE, + 'browsers' => [ + 'IE' => TRUE, + '!IE' => TRUE, + ], + ], + ]; + + $cssCollectionRendererWrapper = new CssCollectionRendererWrapper($this->assetCollectionRendererInterface, $this->assetDataCollector); + $cssCollectionRendererWrapper->render($css); + + $this->assertEquals(1, $this->assetDataCollector->getCssCount()); + + $this->assetDataCollector->collect($this->request, $this->response, $this->exception); + + $data = $this->assetDataCollector->getData(); + $this->assertEquals(AssetsDataCollectorTest::ROOT . '/', $data['assets']['installation_path']); + } + +} diff --git a/web/modules/contrib/devel/webprofiler/tests/src/Unit/DataCollector/CacheDataCollectorTest.php b/web/modules/contrib/devel/webprofiler/tests/src/Unit/DataCollector/CacheDataCollectorTest.php new file mode 100644 index 000000000..a35f16f8b --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/tests/src/Unit/DataCollector/CacheDataCollectorTest.php @@ -0,0 +1,69 @@ +cacheDataCollector = new CacheDataCollector(); + $this->cacheBackendInterface = $this->createMock('Drupal\Core\Cache\CacheBackendInterface'); + } + + /** + * Tests the collection of a cache miss. + */ + public function testCacheCollectorMiss() { + $this->cacheBackendInterface->expects($this->once()) + ->method('get') + ->will($this->returnValue(FALSE)); + + $cacheBackendWrapper = new CacheBackendWrapper($this->cacheDataCollector, $this->cacheBackendInterface, 'default'); + $cache = $cacheBackendWrapper->get('cache_id'); + + $this->assertFalse($cache); + + $this->assertEquals(1, $this->cacheDataCollector->getCacheMissesCount()); + } + + /** + * Tests the collection of a cache hit. + */ + public function testCacheCollectorHit() { + $cache = new \stdClass(); + $cache->cid = 'cache_id'; + $cache->expire = 1; + $cache->tags = ['tag1', 'tag2']; + $this->cacheBackendInterface->expects($this->once()) + ->method('get') + ->will($this->returnValue($cache)); + + $cacheBackendWrapper = new CacheBackendWrapper($this->cacheDataCollector, $this->cacheBackendInterface, 'default'); + $cache2 = $cacheBackendWrapper->get('cache_id'); + + $this->assertNotNull($cache2); + + $this->assertEquals(1, $this->cacheDataCollector->getCacheHitsCount()); + } + +} diff --git a/web/modules/contrib/devel/webprofiler/tests/src/Unit/DataCollector/DataCollectorBaseTest.php b/web/modules/contrib/devel/webprofiler/tests/src/Unit/DataCollector/DataCollectorBaseTest.php new file mode 100644 index 000000000..34284462b --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/tests/src/Unit/DataCollector/DataCollectorBaseTest.php @@ -0,0 +1,40 @@ +request = $this->createMock('Symfony\Component\HttpFoundation\Request'); + $this->response = $this->createMock('Symfony\Component\HttpFoundation\Response'); + $this->exception = $this->createMock('Exception'); + } + +} diff --git a/web/modules/contrib/devel/webprofiler/webprofiler.info.yml b/web/modules/contrib/devel/webprofiler/webprofiler.info.yml new file mode 100644 index 000000000..4b8147670 --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/webprofiler.info.yml @@ -0,0 +1,16 @@ +name: Web Profiler +type: module +description: 'Drupal Web Profiler.' +package: Development +core_version_requirement: ^8.8 || ^9 +php: 7.2 +configure: webprofiler.settings +tags: + - developer +dependencies: + - devel:devel + +# Information added by Drupal.org packaging script on 2020-12-31 +version: '4.1.1' +project: 'devel' +datestamp: 1609419530 diff --git a/web/modules/contrib/devel/webprofiler/webprofiler.install b/web/modules/contrib/devel/webprofiler/webprofiler.install new file mode 100644 index 000000000..47f5c6c3f --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/webprofiler.install @@ -0,0 +1,186 @@ + 'Webprofiler profiles storage.', + 'fields' => [ + 'token' => [ + 'description' => 'Profile token.', + 'type' => 'varchar', + 'length' => 6, + 'not null' => TRUE, + ], + 'data' => [ + 'description' => 'Profile data.', + 'type' => 'text', + 'size' => 'big', + 'not null' => TRUE, + ], + 'ip' => [ + 'description' => 'Request IP.', + 'type' => 'varchar', + 'length' => 64, + 'not null' => TRUE, + ], + 'method' => [ + 'description' => 'Request method.', + 'type' => 'varchar', + 'length' => 6, + 'not null' => TRUE, + ], + 'url' => [ + 'description' => 'Requested URL.', + 'type' => 'varchar', + 'length' => 2048, + 'not null' => TRUE, + ], + 'time' => [ + 'description' => 'Request time.', + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + ], + 'parent' => [ + 'description' => 'Profile parent.', + 'type' => 'varchar', + 'length' => 6, + 'not null' => FALSE, + ], + 'created_at' => [ + 'description' => 'Profile created time.', + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + ], + 'status_code' => [ + 'description' => 'Profile status code.', + 'type' => 'int', + 'size' => 'small', + 'unsigned' => TRUE, + 'not null' => TRUE, + ], + ], + 'indexes' => [ + 'created_at' => ['created_at'], + 'ip' => ['ip'], + 'method' => ['method'], + 'parent' => ['parent'], + ], + 'primary key' => ['token'], + ]; + + return $schema; +} + +/** + * Implements hook_requirements(). + */ +function webprofiler_requirements($phase) { + $requirements = []; + + if ('runtime' == $phase) { + $has_d3 = _webprofiler_verify_library('webprofiler', 'd3'); + $requirements['d3js'] = [ + 'title' => t('D3.js library'), + 'value' => $has_d3 ? t('Enabled') : t('Not found'), + ]; + + if (!$has_d3) { + $requirements['d3js']['severity'] = REQUIREMENT_WARNING; + $requirements['d3js']['description'] = [ + '#prefix' => ' ', + '#markup' => t('Webprofiler module requires D3.js library to properly render data. Composer based install recommended, see README.md file for instructions.'), + ]; + } + + $has_highlight = _webprofiler_verify_library('webprofiler', 'highlightjs'); + $requirements['highlightjs'] = [ + 'title' => t('highlight.js library'), + 'value' => $has_highlight ? t('Enabled') : t('Not found'), + ]; + + if (!$has_highlight) { + $requirements['highlightjs']['severity'] = REQUIREMENT_WARNING; + $requirements['highlightjs']['description'] = [ + '#prefix' => ' ', + '#markup' => t('Webprofiler module requires highlight.js library to syntax highlight collected queries. Composer based install recommended, see README.md file for instructions.'), + ]; + } + } + + return $requirements; +} + +/** + * Verify that the library files exist. + * + * @param string $extension + * The name of the extension that registered a library. + * @param string $name + * The name of a registered library to retrieve. + * + * @return bool + * TRUE if all files of this library exists, FALSE otherwise + * + * @see https://drupal.org/node/2231385 + */ +function _webprofiler_verify_library($extension, $name) { + /** @var Drupal\Core\Asset\LibraryDiscovery $library_discovery */ + $library_discovery = \Drupal::service('library.discovery'); + $library = $library_discovery->getLibraryByName($extension, $name); + + $exist = TRUE; + if ($library['js']) { + foreach ($library['js'] as $js) { + if ($js['type'] == 'file') { + if (!file_exists(DRUPAL_ROOT . '/' . $js['data'])) { + $exist = FALSE; + } + } + } + } + + if ($library['css']) { + foreach ($library['css'] as $css) { + if ($css['type'] == 'file') { + if (!file_exists(DRUPAL_ROOT . '/' . $css['data'])) { + $exist = FALSE; + } + } + } + } + + if ($library['dependencies']) { + foreach ($library['dependencies'] as $dependency) { + $parts = explode('/', $dependency); + $exist = _webprofiler_verify_library($parts[0], $parts[1]); + } + } + + return $exist; +} + +/** + * Add a status_code column to the webprofiler table. + */ +function webprofiler_update_8001() { + $database = \Drupal::database(); + $schema = $database->schema(); + + $spec = [ + 'description' => 'Profile status code.', + 'type' => 'int', + 'size' => 'small', + 'unsigned' => TRUE, + 'not null' => TRUE, + ]; + $schema->addField('webprofiler', 'status_code', $spec); +} diff --git a/web/modules/contrib/devel/webprofiler/webprofiler.libraries.yml b/web/modules/contrib/devel/webprofiler/webprofiler.libraries.yml new file mode 100644 index 000000000..0815a6254 --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/webprofiler.libraries.yml @@ -0,0 +1,63 @@ +d3: + remote: https://github.com/d3/d3 + version: 3.5.17 + license: + name: BSD-3 + url: https://github.com/d3/d3/blob/master/LICENSE + gpl-compatible: true + js: + /libraries/d3/d3.min.js: {} +highlightjs: + remote: https://github.com/highlightjs/highlight.js + version: 9.7.0 + license: + name: BSD-3 + url: https://github.com/highlightjs/highlight.js/blob/master/LICENSE + gpl-compatible: true + js: + /libraries/highlightjs/src/highlight.js: {} + css: + component: + /libraries/highlightjs/src/styles/idea.css: {} +dashboard: + version: 2.0 + js: + js/app/main.js: {} + js/app/helpers.js: {} + js/app/models/collector.js: {} + js/app/collections/collectors.js: {} + js/app/views/collector.js: {} + js/app/views/collectorsList.js: {} + js/app/views/layout.js: {} + js/app/views/details.js: {} + js/app/routers/collectors.js: {} + css: + component: + css/app/dashboard.css: {} + dependencies: + - core/jquery + - core/jquery.once + - core/drupal + - core/drupalSettings + - core/backbone +database: + version: 2.0 + js: + js/database.js: {} + dependencies: + - core/jquery + - core/drupal + - core/drupalSettings + - webprofiler/highlightjs +timeline: + version: 2.0 + js: + js/timeline.js: {} + css: + component: + css/timeline.css: {} + dependencies: + - core/jquery + - core/drupal + - core/drupalSettings + - webprofiler/d3 diff --git a/web/modules/contrib/devel/webprofiler/webprofiler.links.menu.yml b/web/modules/contrib/devel/webprofiler/webprofiler.links.menu.yml new file mode 100644 index 000000000..b2d4a7307 --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/webprofiler.links.menu.yml @@ -0,0 +1,5 @@ +webprofiler.admin_list: + title: 'Webprofiler' + description: 'List saved profiles.' + route_name: webprofiler.admin_list + parent: system.admin_reports diff --git a/web/modules/contrib/devel/webprofiler/webprofiler.links.task.yml b/web/modules/contrib/devel/webprofiler/webprofiler.links.task.yml new file mode 100644 index 000000000..741eb15a9 --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/webprofiler.links.task.yml @@ -0,0 +1,9 @@ +webprofiler.settings: + title: 'Webprofiler' + route_name: webprofiler.settings + base_route: devel.admin_settings + weight: 10 +webprofiler.dashboard: + title: 'dashboard' + route_name: webprofiler.dashboard + base_route: webprofiler.dashboard diff --git a/web/modules/contrib/devel/webprofiler/webprofiler.make.yml b/web/modules/contrib/devel/webprofiler/webprofiler.make.yml new file mode 100644 index 000000000..334519423 --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/webprofiler.make.yml @@ -0,0 +1,12 @@ +core: "8.x" +api: 2 + +libraries: + highlightjs: + download: + type: "git" + url: "https://github.com/highlightjs/highlight.js.git" + d3: + download: + type: "git" + url: "https://github.com/d3/d3.git" diff --git a/web/modules/contrib/devel/webprofiler/webprofiler.module b/web/modules/contrib/devel/webprofiler/webprofiler.module new file mode 100644 index 000000000..01e9b95f1 --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/webprofiler.module @@ -0,0 +1,51 @@ + [ + 'template' => 'Profiler/webprofiler_loader', + 'variables' => ['token' => NULL, 'profiler_url' => NULL], + ], + 'webprofiler_toolbar' => [ + 'template' => 'Profiler/webprofiler_toolbar', + 'variables' => [ + 'token' => NULL, + 'toolbar' => NULL, + ], + ], + 'webprofiler_panel' => [ + 'template' => 'Profiler/webprofiler_panel', + 'variables' => [ + 'panel' => NULL, + ], + ], + 'webprofiler_dashboard' => [ + 'template' => 'Profiler/webprofiler_dashboard', + 'variables' => [ + 'profile' => [], + 'panels' => [], + 'spinner_path' => NULL, + ], + ], + ]; +} + +/** + * Implements hook_cache_flush(). + */ +function webprofiler_cache_flush() { + $config = \Drupal::configFactory()->get('webprofiler.config'); + + if ($config->get('purge_on_cache_clear')) { + $profiler = \Drupal::service('profiler'); + $profiler->purge(); + } +} diff --git a/web/modules/contrib/devel/webprofiler/webprofiler.permissions.yml b/web/modules/contrib/devel/webprofiler/webprofiler.permissions.yml new file mode 100644 index 000000000..b39752527 --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/webprofiler.permissions.yml @@ -0,0 +1,7 @@ +'access webprofiler': + title: 'Access webprofiler' + description: 'Access webprofiler collected data.' + 'restrict access': TRUE +'view webprofiler toolbar': + title: 'View webprofiler toolbar' + description: 'View webprofiler toolbar.' diff --git a/web/modules/contrib/devel/webprofiler/webprofiler.routing.yml b/web/modules/contrib/devel/webprofiler/webprofiler.routing.yml new file mode 100644 index 000000000..11543ecb9 --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/webprofiler.routing.yml @@ -0,0 +1,81 @@ +# toolbar +webprofiler.toolbar: + path: '/profiler/{profile}' + defaults: + _controller: '\Drupal\webprofiler\Controller\ToolbarController::toolbarAction' + options: + parameters: + profile: + type: 'webprofiler:token' + requirements: + _permission: 'view webprofiler toolbar' + +# save frontend data +webprofiler.frontend.save: + path: '/admin/reports/profiler/frontend/{profile}/save' + defaults: + _controller: '\Drupal\webprofiler\Controller\ToolbarController::savePerformanceTimingAction' + _title: 'Save performance timing data' + options: + parameters: + profile: + type: 'webprofiler:token' + methods: [POST] + requirements: + _permission: 'view webprofiler toolbar' + +# view profile +webprofiler.dashboard: + path: '/admin/reports/profiler/view/{profile}' + defaults: + _controller: '\Drupal\webprofiler\Controller\DashboardController::dashboardAction' + _title: 'Webprofiler' + options: + parameters: + profile: + type: 'webprofiler:token' + requirements: + _permission: 'access webprofiler' + +# list stored profiles +webprofiler.admin_list: + path: '/admin/reports/profiler/list' + defaults: + _controller: '\Drupal\webprofiler\Controller\DashboardController::listAction' + _title: 'Webprofiler' + requirements: + _permission: 'access webprofiler' + +webprofiler.rest.collector: + path: '/admin/reports/profiler/view/{profile}/collectors/{collector}' + defaults: + _controller: '\Drupal\webprofiler\Controller\DashboardController::restCollectorAction' + _title: 'Webprofiler' + options: + parameters: + profile: + type: 'webprofiler:token' + requirements: + _permission: 'access webprofiler' + +# get query explain +webprofiler.database.explain: + path: '/admin/reports/profiler/database_explain/{profile}/{qid}' + defaults: + _controller: '\Drupal\webprofiler\Controller\DatabaseController::explainAction' + _title: 'Query explain' + options: + parameters: + profile: + type: 'webprofiler:token' + requirements: + _permission: 'access webprofiler' + +# configure webprofiler +webprofiler.settings: + path: '/admin/config/development/devel/webprofiler' + defaults: + _form: 'Drupal\webprofiler\Form\ConfigForm' + _title: 'Webprofiler settings' + requirements: + _permission: 'access webprofiler' diff --git a/web/modules/contrib/devel/webprofiler/webprofiler.services.yml b/web/modules/contrib/devel/webprofiler/webprofiler.services.yml new file mode 100644 index 000000000..6055a1d00 --- /dev/null +++ b/web/modules/contrib/devel/webprofiler/webprofiler.services.yml @@ -0,0 +1,262 @@ +parameters: + webprofiler.only_exceptions: false + webprofiler.only_master_requests: false + +services: + +# profiler services + logger.channel.webprofiler: + class: Drupal\Core\Logger\LoggerChannel + factory: logger.factory:get + arguments: ['webprofiler'] + + profiler.file_storage: + class: Drupal\webprofiler\Profiler\FileProfilerStorage + arguments: ['%data_collector.storage%'] + tags: + - { name: webprofiler_storage, title: 'File storage' } + + profiler.database_storage: + class: Drupal\webprofiler\Profiler\DatabaseProfilerStorage + arguments: ['@database'] + tags: + - { name: webprofiler_storage, title: 'Database storage' } + + profiler.storage_manager: + class: Drupal\webprofiler\Profiler\ProfilerStorageManager + + profiler.storage: + class: Symfony\Component\HttpKernel\Profiler\ProfilerStorageInterface + factory: Drupal\webprofiler\Profiler\ProfilerStorageFactory::getProfilerStorage + arguments: ['@config.factory', '@service_container'] + + profiler: + class: Drupal\webprofiler\Profiler\Profiler + arguments: ['@profiler.storage', '@logger.channel.webprofiler', '@config.factory'] + +# template manager + template_manager: + class: Drupal\webprofiler\Profiler\TemplateManager + arguments: ['@profiler', '@twig', '@twig.loader', '%data_collector.templates%'] + +# request matcher + webprofiler.matcher: + class: Drupal\webprofiler\RequestMatcher\WebprofilerRequestMatcher + arguments: ['@config.factory', '@path.matcher'] + +# event subscribers + webprofiler.profiler_listener: + class: Drupal\webprofiler\EventSubscriber\ProfilerSubscriber + arguments: ['@profiler', '@request_stack', '@?webprofiler.matcher', '%webprofiler.only_exceptions%', '%webprofiler.only_master_requests%'] + tags: + - { name: event_subscriber } + + webprofiler.webprofiler_event_subscriber: + class: Drupal\webprofiler\EventSubscriber\WebprofilerEventSubscriber + arguments: ['@current_user', '@url_generator', '@renderer'] + tags: + - { name: event_subscriber } + +# twig profiler + twig.profile: + class: Twig_Profiler_Profile + + twig.extension.profiler: + class: Drupal\webprofiler\Twig\Extension\ProfilerExtension + arguments: ['@twig.profile', '@stopwatch', '@webprofiler.ide_link_generator', '@webprofiler.class_shortener'] + tags: + - { name: twig.extension, priority: 100 } + +# datacollector services + webprofiler.drupal: + class: Drupal\webprofiler\DataCollector\DrupalDataCollector + arguments: ['@redirect.destination', '@url_generator'] + tags: + - { name: data_collector, template: '@webprofiler/Collector/drupal.html.twig', id: 'drupal', title: 'Drupal', priority: 10 } + + webprofiler.devel: + class: Drupal\webprofiler\DataCollector\DevelDataCollector + arguments: ['@menu.link_tree'] + tags: + - { name: data_collector, template: '@webprofiler/Collector/devel.html.twig', id: 'devel', title: 'Devel', priority: 15 } + + webprofiler.php_config: + class: Drupal\webprofiler\DataCollector\PhpConfigDataCollector + tags: + - { name: data_collector, template: '@webprofiler/Collector/php_config.html.twig', id: 'php_config', title: 'PHP Config', priority: 20 } + + webprofiler.request: + class: Drupal\webprofiler\DataCollector\RequestDataCollector + arguments: ['@controller_resolver'] + tags: + - { name: data_collector, template: '@webprofiler/Collector/request.html.twig', id: 'request', title: 'Request', priority: 30 } + - { name: event_subscriber } + + webprofiler.time: + class: Drupal\webprofiler\DataCollector\TimeDataCollector + arguments: [NULL, '@stopwatch'] + tags: + - { name: data_collector, template: '@webprofiler/Collector/time.html.twig', id: 'time', title: 'Timeline', priority: 40 } + + webprofiler.performance_timing: + class: Drupal\webprofiler\DataCollector\PerformanceTimingDataCollector + tags: + - { name: data_collector, template: '@webprofiler/Collector/performance_timing.html.twig', id: 'performance_timing', title: 'Performance Timing', priority: 50 } + + webprofiler.database: + class: Drupal\webprofiler\DataCollector\DatabaseDataCollector + arguments: ['@database', '@config.factory'] + tags: + - { name: data_collector, template: '@webprofiler/Collector/database.html.twig', id: 'database', title: 'Database', priority: 60 } + + webprofiler.user: + class: Drupal\webprofiler\DataCollector\UserDataCollector + arguments: ['@current_user', '@entity_type.manager', '@config.factory', '@authentication_collector'] + tags: + - { name: data_collector, template: '@webprofiler/Collector/user.html.twig', id: 'user', title: 'User', priority: 70 } + + webprofiler.forms: + class: Drupal\webprofiler\DataCollector\FormsDataCollector + arguments: ['@form_builder'] + tags: + - { name: data_collector, template: '@webprofiler/Collector/forms.html.twig', id: 'forms', title: 'Forms', priority: 80 } + + webprofiler.drupal_extensions: + class: Drupal\webprofiler\DataCollector\ExtensionDataCollector + arguments: ['@module_handler', '@theme_handler', '@app.root'] + tags: + - { name: data_collector, template: '@webprofiler/Collector/extensions.html.twig', id: 'drupal_extension', title: 'Extensions', priority: 90 } + + webprofiler.routing: + class: Drupal\webprofiler\DataCollector\RoutingDataCollector + arguments: ['@router.route_provider'] + tags: + - { name: data_collector, template: '@webprofiler/Collector/routing.html.twig', id: 'routing', title: 'Routing', priority: 100 } + + webprofiler.cache: + class: Drupal\webprofiler\DataCollector\CacheDataCollector + tags: + - { name: data_collector, template: '@webprofiler/Collector/cache.html.twig', id: 'cache', title: 'Cache', priority: 110 } + + webprofiler.assets: + class: Drupal\webprofiler\DataCollector\AssetsDataCollector + arguments: ['@app.root'] + tags: + - { name: data_collector, template: '@webprofiler/Collector/assets.html.twig', id: 'assets', title: 'Assets', priority: 120} + + webprofiler.config: + class: Drupal\webprofiler\DataCollector\ConfigDataCollector + tags: + - { name: data_collector, template: '@webprofiler/Collector/config.html.twig', id: 'config', title: 'Config', priority: 130 } + + webprofiler.state: + class: Drupal\webprofiler\DataCollector\StateDataCollector + tags: + - { name: data_collector, template: '@webprofiler/Collector/state.html.twig', id: 'state', title: 'State', priority: 140 } + + webprofiler.events: + class: Drupal\webprofiler\DataCollector\EventsDataCollector + arguments: ['@event_dispatcher'] + tags: + - { name: data_collector, template: '@webprofiler/Collector/events.html.twig', id: 'events', title: 'Events', priority: 150 } + + webprofiler.services: + class: Drupal\webprofiler\DataCollector\ServicesDataCollector + arguments: ['@service_container'] + tags: + - { name: data_collector, template: '@webprofiler/Collector/services.html.twig', id: 'services', title: 'Services', priority: 160 } + + webprofiler.http: + class: Drupal\webprofiler\DataCollector\HttpDataCollector + arguments: ['@http_client_middleware.webprofiler'] + tags: + - { name: data_collector, template: '@webprofiler/Collector/http.html.twig', id: 'http', title: 'Http', priority: 170 } + + webprofiler.theme: + class: Drupal\webprofiler\DataCollector\ThemeDataCollector + arguments: ['@theme.manager', '@theme.negotiator', '@twig.profile'] + tags: + - { name: data_collector, template: '@webprofiler/Collector/theme.html.twig', id: 'theme', title: 'Theme', priority: 180 } + + webprofiler.mail: + class: Drupal\webprofiler\DataCollector\MailDataCollector + tags: + - { name: data_collector, template: '@webprofiler/Collector/mail.html.twig', id: 'mail', title: 'Mail', priority: 190 } + +# debug services + stopwatch: + class: Drupal\webprofiler\Stopwatch + + webprofiler.debug.plugin.manager.mail.default: + class: Drupal\Core\Mail\MailManager + arguments: ['@container.namespaces', '@cache.discovery', '@module_handler', '@config.factory', '@logger.factory', '@string_translation', '@renderer'] + + webprofiler.debug.controller_resolver: + class: Symfony\Component\HttpKernel\Controller\TraceableControllerResolver + arguments: ['@controller_resolver', '@stopwatch'] + + webprofiler.debug.cache_factory: + class: Drupal\webprofiler\Cache\CacheFactoryWrapper + public: false + decorates: cache_factory + arguments: ['@webprofiler.debug.cache_factory.inner', '@webprofiler.cache'] + properties: + _serviceId: 'cache_factory' + + webprofiler.debug.asset.css.collection_renderer: + class: Drupal\webprofiler\Asset\CssCollectionRendererWrapper + public: false + decorates: asset.css.collection_renderer + arguments: ['@webprofiler.debug.asset.css.collection_renderer.inner', '@webprofiler.assets'] + properties: + _serviceId: 'asset.css.collection_renderer' + + webprofiler.debug.asset.js.collection_renderer: + class: Drupal\webprofiler\Asset\JsCollectionRendererWrapper + public: false + decorates: asset.js.collection_renderer + arguments: ['@webprofiler.debug.asset.js.collection_renderer.inner', '@webprofiler.assets'] + properties: + _serviceId: 'asset.js.collection_renderer' + + webprofiler.debug.entity_type.manager: + class: Drupal\webprofiler\Entity\EntityManagerWrapper + public: false + decorates: entity_type.manager + arguments: ['@webprofiler.debug.entity_type.manager.inner'] + properties: + _serviceId: 'entity_type.manager' + + webprofiler.debug.state: + class: Drupal\webprofiler\State\StateWrapper + public: false + decorates: state + arguments: ['@webprofiler.debug.state.inner', '@webprofiler.state'] + properties: + _serviceId: 'state' + +# middleware + http_middleware.webprofiler: + class: Drupal\webprofiler\StackMiddleware\WebprofilerMiddleware + tags: + - { name: http_middleware, priority: 350 } + + http_client_middleware.webprofiler: + class: Drupal\webprofiler\Http\HttpClientMiddleware + tags: + - { name: http_client_middleware } + +# parameter converter service for profile token + webprofiler.token_converter: + class: Drupal\webprofiler\Routing\TokenConverter + tags: + - { name: paramconverter } + +# IDE link generator service + webprofiler.ide_link_generator: + class: Drupal\webprofiler\Helper\IdeLinkGenerator + arguments: ['@config.factory'] + +# class shortener service + webprofiler.class_shortener: + class: Drupal\webprofiler\Helper\ClassShortener