diff --git a/.all-contributorsrc b/.all-contributorsrc index 6287f526d1a..7d2796966ca 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -3122,6 +3122,15 @@ "contributions": [ "code" ] + }, + { + "login": "sobotama", + "name": "sobotama", + "avatar_url": "https://avatars.githubusercontent.com/u/77323555?v=4", + "profile": "https://github.com/sobotama", + "contributions": [ + "code" + ] } ], "skipCi": true, diff --git a/src/OrchardCore.Modules/OrchardCore.ContentFields/GraphQL/Startup.cs b/src/OrchardCore.Modules/OrchardCore.ContentFields/GraphQL/Startup.cs index 7baaf362ede..6eff7d9ab16 100644 --- a/src/OrchardCore.Modules/OrchardCore.ContentFields/GraphQL/Startup.cs +++ b/src/OrchardCore.Modules/OrchardCore.ContentFields/GraphQL/Startup.cs @@ -19,6 +19,7 @@ public override void ConfigureServices(IServiceCollection services) services.AddObjectGraphType(); services.AddObjectGraphType(); services.AddObjectGraphType(); + services.AddObjectGraphType(); } } } diff --git a/src/OrchardCore.Modules/OrchardCore.ContentFields/GraphQL/Types/UserPickerFieldQueryObjectType.cs b/src/OrchardCore.Modules/OrchardCore.ContentFields/GraphQL/Types/UserPickerFieldQueryObjectType.cs new file mode 100644 index 00000000000..8f64ae91467 --- /dev/null +++ b/src/OrchardCore.Modules/OrchardCore.ContentFields/GraphQL/Types/UserPickerFieldQueryObjectType.cs @@ -0,0 +1,64 @@ +using System.Collections.Generic; +using System.Linq; +using GraphQL; +using GraphQL.DataLoader; +using GraphQL.Types; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Localization; +using OrchardCore.Apis.GraphQL; +using OrchardCore.ContentFields.Fields; +using OrchardCore.ContentManagement; +using OrchardCore.Users.GraphQL; +using OrchardCore.Users.Indexes; +using OrchardCore.Users.Models; +using YesSql; +using YesSql.Services; + +namespace OrchardCore.ContentFields.GraphQL +{ + public class UserPickerFieldQueryObjectType : ObjectGraphType + { + public UserPickerFieldQueryObjectType(IStringLocalizer S) + { + Name = nameof(UserPickerField); + + Field, IEnumerable>("userIds") + .Description(S["user ids"]) + .PagingArguments() + .Resolve(resolve => + { + return resolve.Page(resolve.Source.UserIds); + }); + + Field, IEnumerable>("users") + .Description(S["the user items"]) + .PagingArguments() + .ResolveAsync(resolve => + { + var userLoader = GetOrAddUserProfileByIdDataLoader(resolve); + return userLoader.LoadAsync(resolve.Page(resolve.Source.UserIds)).Then(itemResultSet => + { + return itemResultSet.SelectMany(users => users); + }); + }); + } + + private static IDataLoader> GetOrAddUserProfileByIdDataLoader(IResolveFieldContext context) + { + var dataLoaderContextAccessor = context.RequestServices.GetRequiredService(); + + return dataLoaderContextAccessor.Context.GetOrAddCollectionBatchLoader("GetOrAddUserByIds", async (IEnumerable userIds) => + { + if (userIds == null || !userIds.Any()) + { + return default; + } + + var session = context.RequestServices.GetService(); + var users = await session.Query(user => user.UserId.IsIn(userIds)).ListAsync(); + + return users.ToLookup(user => user.UserId); + }); + } + } +} diff --git a/src/OrchardCore.Modules/OrchardCore.ContentFields/OrchardCore.ContentFields.csproj b/src/OrchardCore.Modules/OrchardCore.ContentFields/OrchardCore.ContentFields.csproj index d1666948809..4bfe9e3d2aa 100644 --- a/src/OrchardCore.Modules/OrchardCore.ContentFields/OrchardCore.ContentFields.csproj +++ b/src/OrchardCore.Modules/OrchardCore.ContentFields/OrchardCore.ContentFields.csproj @@ -33,6 +33,7 @@ + diff --git a/src/OrchardCore.Modules/OrchardCore.Rules/Drivers/JavascriptConditionDisplayDriver.cs b/src/OrchardCore.Modules/OrchardCore.Rules/Drivers/JavascriptConditionDisplayDriver.cs index 92b439af998..ec85a37b5db 100644 --- a/src/OrchardCore.Modules/OrchardCore.Rules/Drivers/JavascriptConditionDisplayDriver.cs +++ b/src/OrchardCore.Modules/OrchardCore.Rules/Drivers/JavascriptConditionDisplayDriver.cs @@ -1,14 +1,39 @@ +using System; using System.Threading.Tasks; +using Esprima; +using Jint.Runtime; +using Microsoft.AspNetCore.Mvc.Localization; +using Microsoft.Extensions.Localization; using OrchardCore.DisplayManagement.Handlers; using OrchardCore.DisplayManagement.ModelBinding; +using OrchardCore.DisplayManagement.Notify; using OrchardCore.DisplayManagement.Views; +using OrchardCore.Mvc.ModelBinding; using OrchardCore.Rules.Models; +using OrchardCore.Rules.Services; using OrchardCore.Rules.ViewModels; namespace OrchardCore.Rules.Drivers { public class JavascriptConditionDisplayDriver : DisplayDriver { + private readonly IHtmlLocalizer H; + private readonly IStringLocalizer S; + private readonly INotifier _notifier; + private readonly JavascriptConditionEvaluator _evaluator; + + public JavascriptConditionDisplayDriver( + IHtmlLocalizer htmlLocalizer, + IStringLocalizer stringLocalizer, + JavascriptConditionEvaluator evaluator, + INotifier notifier) + { + H = htmlLocalizer; + S = stringLocalizer; + _evaluator = evaluator; + _notifier = notifier; + } + public override IDisplayResult Display(JavascriptCondition condition) { return @@ -32,8 +57,40 @@ public override async Task UpdateAsync(JavascriptCondition condi var model = new JavascriptConditionViewModel(); await updater.TryUpdateModelAsync(model, Prefix); - // TODO is empty. - condition.Script = model.Script; + // CodeMirror hides the textarea which displays the error when updater.ModelState.AddModelError() is used, + // that's why a notifier is used to show validation errors. + if (string.IsNullOrWhiteSpace(model.Script)) + { + updater.ModelState.AddModelError(Prefix, nameof(model.Script), S["Please provide a script."]); + await _notifier.ErrorAsync(H["Please provide a script."]); + return Edit(condition); + } + + try + { + _ = await _evaluator.EvaluateAsync(new() + { + ConditionId = condition.ConditionId, + Name = condition.Name, + Script = model.Script + }); + condition.Script = model.Script; + } + catch (ParserException ex) // Invalid syntax + { + updater.ModelState.AddModelError(Prefix, nameof(model.Script), S["The script couldn't be parsed. Details: {0}", ex.Message]); + await _notifier.ErrorAsync(H["The script couldn't be parsed. Details: {0}", ex.Message]); + } + catch (JavaScriptException ex) // Evaluation threw an Error + { + updater.ModelState.AddModelError(Prefix, nameof(model.Script), S["JavaScript evaluation resulted in an exception. Details: {0}", ex.Message]); + await _notifier.ErrorAsync(H["JavaScript evaluation resulted in an exception. Details: {0}", ex.Message]); + } + catch (Exception ex) when (ex is InvalidCastException or FormatException) // Evaluation completes successfully, but the result cannot be converted to Boolean + { + updater.ModelState.AddModelError(Prefix, nameof(model.Script), S["The script evaluation failed. Details: {0}", ex.Message]); + await _notifier.ErrorAsync(H["The script evaluation failed. Details: {0}", ex.Message]); + } return Edit(condition); } diff --git a/src/OrchardCore.Modules/OrchardCore.Rules/OrchardCore.Rules.csproj b/src/OrchardCore.Modules/OrchardCore.Rules/OrchardCore.Rules.csproj index 5ec2e46ac53..826283775c7 100644 --- a/src/OrchardCore.Modules/OrchardCore.Rules/OrchardCore.Rules.csproj +++ b/src/OrchardCore.Modules/OrchardCore.Rules/OrchardCore.Rules.csproj @@ -14,6 +14,10 @@ + + + + diff --git a/src/OrchardCore.Modules/OrchardCore.Users/Controllers/AccountController.cs b/src/OrchardCore.Modules/OrchardCore.Users/Controllers/AccountController.cs index 3e88f66dc4e..221e2402247 100644 --- a/src/OrchardCore.Modules/OrchardCore.Users/Controllers/AccountController.cs +++ b/src/OrchardCore.Modules/OrchardCore.Users/Controllers/AccountController.cs @@ -312,7 +312,7 @@ private async Task ExternalLoginSignInAsync(IUser user, ExternalLo } catch (Exception ex) { - _logger.LogError(ex, "{externalLoginHandler} - IExternalLoginHandler.UpdateRoles threw an exception", item.GetType()); + _logger.LogError(ex, "{ExternalLoginHandler}.UpdateRoles threw an exception", item.GetType()); } } diff --git a/src/OrchardCore.Modules/OrchardCore.Users/Controllers/AdminController.cs b/src/OrchardCore.Modules/OrchardCore.Users/Controllers/AdminController.cs index 59e92d28eef..ff6cb9297c1 100644 --- a/src/OrchardCore.Modules/OrchardCore.Users/Controllers/AdminController.cs +++ b/src/OrchardCore.Modules/OrchardCore.Users/Controllers/AdminController.cs @@ -499,7 +499,7 @@ public async Task EditPassword(string id) return Forbid(); } - var model = new ResetPasswordViewModel { Identifier = user.UserName }; + var model = new ResetPasswordViewModel { UsernameOrEmail = user.UserName }; return View(model); } @@ -507,7 +507,7 @@ public async Task EditPassword(string id) [HttpPost] public async Task EditPassword(ResetPasswordViewModel model) { - if (await _userService.GetUserAsync(model.Identifier) is not User user) + if (await _userService.GetUserAsync(model.UsernameOrEmail) is not User user) { return NotFound(); } @@ -521,7 +521,7 @@ public async Task EditPassword(ResetPasswordViewModel model) { var token = await _userManager.GeneratePasswordResetTokenAsync(user); - if (await _userService.ResetPasswordAsync(model.Identifier, token, model.NewPassword, ModelState.AddModelError)) + if (await _userService.ResetPasswordAsync(model.UsernameOrEmail, token, model.NewPassword, ModelState.AddModelError)) { await _notifier.SuccessAsync(H["Password updated correctly."]); diff --git a/src/OrchardCore.Modules/OrchardCore.Users/Controllers/ResetPasswordController.cs b/src/OrchardCore.Modules/OrchardCore.Users/Controllers/ResetPasswordController.cs index 298ab953f3e..ce825aea29f 100644 --- a/src/OrchardCore.Modules/OrchardCore.Users/Controllers/ResetPasswordController.cs +++ b/src/OrchardCore.Modules/OrchardCore.Users/Controllers/ResetPasswordController.cs @@ -93,7 +93,7 @@ public async Task ForgotPasswordPOST() if (ModelState.IsValid) { - var user = await _userService.GetForgotPasswordUserAsync(model.Identifier) as User; + var user = await _userService.GetForgotPasswordUserAsync(model.UsernameOrEmail) as User; if (user == null || await MustValidateEmailAsync(user)) { // returns to confirmation page anyway: we don't want to let scrapers know if a username or an email exist @@ -166,7 +166,7 @@ public async Task ResetPasswordPOST() { var token = Encoding.UTF8.GetString(Convert.FromBase64String(model.ResetToken)); - if (await _userService.ResetPasswordAsync(model.Identifier, token, model.NewPassword, ModelState.AddModelError)) + if (await _userService.ResetPasswordAsync(model.UsernameOrEmail, token, model.NewPassword, ModelState.AddModelError)) { return RedirectToAction(nameof(ResetPasswordConfirmation)); } diff --git a/src/OrchardCore.Modules/OrchardCore.Users/Drivers/ForgotPasswordFormDisplayDriver.cs b/src/OrchardCore.Modules/OrchardCore.Users/Drivers/ForgotPasswordFormDisplayDriver.cs index cc087b1efb7..c6b6bd6c5e6 100644 --- a/src/OrchardCore.Modules/OrchardCore.Users/Drivers/ForgotPasswordFormDisplayDriver.cs +++ b/src/OrchardCore.Modules/OrchardCore.Users/Drivers/ForgotPasswordFormDisplayDriver.cs @@ -13,7 +13,7 @@ public override IDisplayResult Edit(ForgotPasswordForm model) { return Initialize("ForgotPasswordFormIdentifier", vm => { - vm.Identifier = model.Identifier; + vm.UsernameOrEmail = model.UsernameOrEmail; }).Location("Content"); } @@ -23,7 +23,7 @@ public override async Task UpdateAsync(ForgotPasswordForm model, await updater.TryUpdateModelAsync(viewModel, Prefix); - model.Identifier = viewModel.Identifier; + model.UsernameOrEmail = viewModel.UsernameOrEmail; return Edit(model); } diff --git a/src/OrchardCore.Modules/OrchardCore.Users/Drivers/ResetPasswordFormDisplayDriver.cs b/src/OrchardCore.Modules/OrchardCore.Users/Drivers/ResetPasswordFormDisplayDriver.cs index d188e00c29b..8b5e2b1723c 100644 --- a/src/OrchardCore.Modules/OrchardCore.Users/Drivers/ResetPasswordFormDisplayDriver.cs +++ b/src/OrchardCore.Modules/OrchardCore.Users/Drivers/ResetPasswordFormDisplayDriver.cs @@ -13,7 +13,7 @@ public override IDisplayResult Edit(ResetPasswordForm model) { return Initialize("ResetPasswordFormIdentifier", vm => { - vm.Identifier = model.Identifier; + vm.UsernameOrEmail = model.UsernameOrEmail; vm.NewPassword = model.NewPassword; vm.ResetToken = model.ResetToken; }).Location("Content"); @@ -25,7 +25,7 @@ public override async Task UpdateAsync(ResetPasswordForm model, await updater.TryUpdateModelAsync(vm, Prefix); - model.Identifier = vm.Identifier; + model.UsernameOrEmail = vm.UsernameOrEmail; model.NewPassword = vm.NewPassword; model.ResetToken = vm.ResetToken; diff --git a/src/OrchardCore.Modules/OrchardCore.Users/ViewModels/ForgotPasswordViewModel.cs b/src/OrchardCore.Modules/OrchardCore.Users/ViewModels/ForgotPasswordViewModel.cs index 2fac0702b57..0bb15c54d1a 100644 --- a/src/OrchardCore.Modules/OrchardCore.Users/ViewModels/ForgotPasswordViewModel.cs +++ b/src/OrchardCore.Modules/OrchardCore.Users/ViewModels/ForgotPasswordViewModel.cs @@ -5,11 +5,11 @@ namespace OrchardCore.Users.ViewModels { public class ForgotPasswordViewModel { - [Obsolete("Email property is no longer used and will be removed in future releases. Instead use Identifier.")] + [Obsolete("Email property is no longer used and will be removed in future releases. Instead use UsernameOrEmail.")] [Email.EmailAddress(ErrorMessage = "Invalid Email.")] public string Email { get; set; } [Required(ErrorMessage = "Username or email address is required.")] - public string Identifier { get; set; } + public string UsernameOrEmail { get; set; } } } diff --git a/src/OrchardCore.Modules/OrchardCore.Users/ViewModels/ResetPasswordViewModel.cs b/src/OrchardCore.Modules/OrchardCore.Users/ViewModels/ResetPasswordViewModel.cs index 2e30d1bfb5d..4c81b332c3e 100644 --- a/src/OrchardCore.Modules/OrchardCore.Users/ViewModels/ResetPasswordViewModel.cs +++ b/src/OrchardCore.Modules/OrchardCore.Users/ViewModels/ResetPasswordViewModel.cs @@ -5,12 +5,12 @@ namespace OrchardCore.Users.ViewModels { public class ResetPasswordViewModel { - [Obsolete("Email property is no longer used and will be removed in future releases. Instead use Identifier.")] + [Obsolete("Email property is no longer used and will be removed in future releases. Instead use UsernameOrEmail.")] [Email.EmailAddress(ErrorMessage = "Invalid Email.")] public string Email { get; set; } [Required(ErrorMessage = "Username or email address is required.")] - public string Identifier { get; set; } + public string UsernameOrEmail { get; set; } [Required(ErrorMessage = "New password is required.")] [DataType(DataType.Password)] diff --git a/src/OrchardCore.Modules/OrchardCore.Users/Views/Admin/EditPassword.cshtml b/src/OrchardCore.Modules/OrchardCore.Users/Views/Admin/EditPassword.cshtml index fffe05f3d5b..353636bb537 100644 --- a/src/OrchardCore.Modules/OrchardCore.Users/Views/Admin/EditPassword.cshtml +++ b/src/OrchardCore.Modules/OrchardCore.Users/Views/Admin/EditPassword.cshtml @@ -4,7 +4,7 @@
- +
diff --git a/src/OrchardCore.Modules/OrchardCore.Users/Views/ForgotPasswordFormIdentifier.cshtml b/src/OrchardCore.Modules/OrchardCore.Users/Views/ForgotPasswordFormIdentifier.cshtml index 4a1388eba44..a4a2be2b2a2 100644 --- a/src/OrchardCore.Modules/OrchardCore.Users/Views/ForgotPasswordFormIdentifier.cshtml +++ b/src/OrchardCore.Modules/OrchardCore.Users/Views/ForgotPasswordFormIdentifier.cshtml @@ -1,6 +1,6 @@ @model ForgotPasswordViewModel
- - + +
diff --git a/src/OrchardCore.Modules/OrchardCore.Users/Views/ResetPasswordFormIdentifier.cshtml b/src/OrchardCore.Modules/OrchardCore.Users/Views/ResetPasswordFormIdentifier.cshtml index d2fa7d15272..32be249eda9 100644 --- a/src/OrchardCore.Modules/OrchardCore.Users/Views/ResetPasswordFormIdentifier.cshtml +++ b/src/OrchardCore.Modules/OrchardCore.Users/Views/ResetPasswordFormIdentifier.cshtml @@ -3,9 +3,9 @@
- - - + + +
diff --git a/src/OrchardCore/OrchardCore.Users.Abstractions/Services/IUserService.cs b/src/OrchardCore/OrchardCore.Users.Abstractions/Services/IUserService.cs index 8cbf8a7bb20..0d668f59e59 100644 --- a/src/OrchardCore/OrchardCore.Users.Abstractions/Services/IUserService.cs +++ b/src/OrchardCore/OrchardCore.Users.Abstractions/Services/IUserService.cs @@ -12,11 +12,11 @@ public interface IUserService /// /// Authenticates the user credentials. /// - /// The username or email address. + /// The username or email address. /// The user password. /// The error reported in case failure happened during the authentication process. /// A that represents an authenticated user. - Task AuthenticateAsync(string identifier, string password, Action reportError); + Task AuthenticateAsync(string usernameOrEmail, string password, Action reportError); /// /// Creates a user. @@ -56,9 +56,9 @@ public interface IUserService /// /// Gets the user with a specified username or email address. /// - /// The username or email address. + /// The username or email address. /// The represents the retrieved user. - Task GetUserAsync(string identifier); + Task GetUserAsync(string usernameOrEmail); /// /// Gets the user with a specified ID. @@ -77,12 +77,12 @@ public interface IUserService /// /// Resets the user password. /// - /// The username or email address. + /// The username or email address. /// The token used to reset the password. /// The new password. /// The error reported in case failure happened during the reset process. /// Returns true if the password reset, otherwise false. - Task ResetPasswordAsync(string identifier, string resetToken, string newPassword, Action reportError); + Task ResetPasswordAsync(string usernameOrEmail, string resetToken, string newPassword, Action reportError); /// /// Creates a for a given user. diff --git a/src/OrchardCore/OrchardCore.Users.Core/Models/ForgotPasswordForm.cs b/src/OrchardCore/OrchardCore.Users.Core/Models/ForgotPasswordForm.cs index cfe629664bb..3618e5116dc 100644 --- a/src/OrchardCore/OrchardCore.Users.Core/Models/ForgotPasswordForm.cs +++ b/src/OrchardCore/OrchardCore.Users.Core/Models/ForgotPasswordForm.cs @@ -4,5 +4,5 @@ namespace OrchardCore.Users.Models; public class ForgotPasswordForm : Entity { - public string Identifier { get; set; } + public string UsernameOrEmail { get; set; } } diff --git a/src/OrchardCore/OrchardCore.Users.Core/Models/ResetPasswordForm.cs b/src/OrchardCore/OrchardCore.Users.Core/Models/ResetPasswordForm.cs index d5dfc31a784..a8306592926 100644 --- a/src/OrchardCore/OrchardCore.Users.Core/Models/ResetPasswordForm.cs +++ b/src/OrchardCore/OrchardCore.Users.Core/Models/ResetPasswordForm.cs @@ -4,7 +4,7 @@ namespace OrchardCore.Users.Models; public class ResetPasswordForm : Entity { - public string Identifier { get; set; } + public string UsernameOrEmail { get; set; } public string NewPassword { get; set; } diff --git a/src/OrchardCore/OrchardCore.Users.Core/Services/UserService.cs b/src/OrchardCore/OrchardCore.Users.Core/Services/UserService.cs index f05c7eb8bbc..2e40d3110f1 100644 --- a/src/OrchardCore/OrchardCore.Users.Core/Services/UserService.cs +++ b/src/OrchardCore/OrchardCore.Users.Core/Services/UserService.cs @@ -45,7 +45,7 @@ public UserService( _logger = logger; } - public async Task AuthenticateAsync(string identifier, string password, Action reportError) + public async Task AuthenticateAsync(string usernameOrEmail, string password, Action reportError) { var disableLocalLogin = (await _siteService.GetSiteSettingsAsync()).As().DisableLocalLogin; @@ -55,7 +55,7 @@ public async Task AuthenticateAsync(string identifier, string password, A return null; } - if (string.IsNullOrWhiteSpace(identifier)) + if (string.IsNullOrWhiteSpace(usernameOrEmail)) { reportError("Username", S["A user name is required."]); return null; @@ -67,7 +67,7 @@ public async Task AuthenticateAsync(string identifier, string password, A return null; } - var user = await GetUserAsync(identifier); + var user = await GetUserAsync(usernameOrEmail); if (user == null) { reportError(string.Empty, S["The specified username/password couple is invalid."]); @@ -184,12 +184,12 @@ public async Task GetForgotPasswordUserAsync(string userId) return user; } - public async Task ResetPasswordAsync(string identifier, string resetToken, string newPassword, Action reportError) + public async Task ResetPasswordAsync(string usernameOrEmail, string resetToken, string newPassword, Action reportError) { var result = true; - if (string.IsNullOrWhiteSpace(identifier)) + if (string.IsNullOrWhiteSpace(usernameOrEmail)) { - reportError(nameof(ResetPasswordForm.Identifier), S["A username or email address is required."]); + reportError(nameof(ResetPasswordForm.UsernameOrEmail), S["A username or email address is required."]); result = false; } @@ -210,7 +210,7 @@ public async Task ResetPasswordAsync(string identifier, string resetToken, return result; } - var user = await GetUserAsync(identifier) as User; + var user = await GetUserAsync(usernameOrEmail) as User; if (user == null) { @@ -244,19 +244,20 @@ public Task CreatePrincipalAsync(IUser user) return _signInManager.CreateUserPrincipalAsync(user); } - public async Task GetUserAsync(string identifier) + public async Task GetUserAsync(string usernameOrEmail) { - var user = await _userManager.FindByNameAsync(identifier); + var user = await _userManager.FindByNameAsync(usernameOrEmail); if (user is null && _identityOptions.User.RequireUniqueEmail) { - user = await _userManager.FindByEmailAsync(identifier); + user = await _userManager.FindByEmailAsync(usernameOrEmail); } return user; } - public Task GetUserByUniqueIdAsync(string userIdentifier) => _userManager.FindByIdAsync(userIdentifier); + public Task GetUserByUniqueIdAsync(string userId) + => _userManager.FindByIdAsync(userId); public void ProcessValidationErrors(IEnumerable errors, User user, Action reportError) { diff --git a/src/docs/community/contributors/README.md b/src/docs/community/contributors/README.md index 4aea67c9de7..606f4afb9e2 100644 --- a/src/docs/community/contributors/README.md +++ b/src/docs/community/contributors/README.md @@ -1,7 +1,7 @@ # Contributors ✨ -[![All Contributors](https://img.shields.io/badge/all_contributors-337-orange.svg?style=flat-square)](#contributors-) +[![All Contributors](https://img.shields.io/badge/all_contributors-338-orange.svg?style=flat-square)](#contributors-) Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key))! @@ -467,6 +467,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d Kartheek Penagamuri
Kartheek Penagamuri

💻 + sobotama
sobotama

💻 diff --git a/src/docs/releases/1.9.0.md b/src/docs/releases/1.9.0.md index 9de49f1e826..8c334fe1fda 100644 --- a/src/docs/releases/1.9.0.md +++ b/src/docs/releases/1.9.0.md @@ -163,7 +163,7 @@ public class ReCaptchaResetPasswordFormDisplayDriver : DisplayDriver`. For example, the ReCaptcha shape is injected using the following driver: