Drupal 8 subscribe to an event from inside block - drupal-8

I am currently trying to wrap my head around Drupal 8 module development best practices. All I'm trying to do is to have a simple form Demoform on a page where a user can input an email address. When the form gets submitted I'd like to dispatch an event demo_form.save. Also I need a block that then displays the user's email address within the block (let's say sidebar second). I have already implemented an EventSubscriber before as a test, so the event gets properly dispatched etc. and I also subscribed to the event (but how to get the information inside a block) Now my question: what's the best practice for this workflow:
File DemoForm.php
class DemoForm extends ConfigFormBase {
...
$event = $dispatcher->dispatch('demo_form.save', $e);
...
}
File DemoEventSubscriber.php
class DemoEventSubscriber implements EventSubscriberInterface {
static function getSubscribedEvents() {
$events['demo_form.save'][] = array('onConfigSave', 0);
return $events;
}
public function onConfigSave($event) {
...
}
}
This works and I can access the input from the form inside the DemoEventSubscriber class and do whatever I want with it.
But now I'd like to display the email address inside the block markup. How should this best be done ?
File DemoBlock.php
class DemoBlock extends BlockBase {
public function build() {
// here return markup with email address from form
}
}
How do I combine the eventsubscriber and the block markup ? Can Blockbase itself implement the EventSubscriberInterface and be independent from DemoEventSubscriber.php ? Or do I need to register a service that transmits the form data and then access the service within the block's build() function ? Or is there another way I am missing ?
Thanks for any input.

I am not sure what you need the event for, but to dispatch the event, use the code you have already displayed in your submitForm() function of the DemoForm class.
Because you are using ConfigFormBase, I assume that you want to store the submitted e-mail address in config, use code like from the config form documentation:
/**
* {#inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
// Retrieve the configuration
$this->config('mymodule.settings')
// Set the submitted configuration setting
->set('email', $form_state->getValue('email'))
->save();
// Assuming you have injected the dispatcher.
$event = $this->dispatcher->dispatch('demo_form.save', $e);
parent::submitForm($form, $form_state);
}
Within you block, you can then access the configuration for example with the static wrapper or inject the service Simple Configuration API
$config = \Drupal::config('mymodule.settings');
$message = $config->get('email');
Note that with this you can always set only one e-mail address. I don't know if that was your purpose. If you want to collect multiple e-mails then you should store them in the database and not in config.

Related

How to check "_custom_access" for whole website and not module/path?

example:
path: '/example'
defaults:
_controller: '\Drupal\example\Controller\ExampleController::content'
requirements:
_custom_access: '\Drupal\example\Controller\ExampleController::access'
This custom_access checker will be executed only when someone call mywebsite.domain/example.
But I want that this controller check all urls, run independent of path.
How can I create an independent custom access controller?
The idea for preventing routing access to a very low level (Kernel one to be precise), is to register a EventSubscriber service, subscribing to the REQUEST KernelEvent.
First of all, you will need to create a new custom module.
Once done, you will be able to create a new my_module.services.yml file which will declare a new EventSubscriber
services:
my_module.subscriber:
class: Drupal\my_module\EventSubscriber\MyCustomSubscriber
tags:
- { name: event_subscriber}
Then, create the class referenced above in my_module/src/EventSubscriber/MyCustomSubscriber.php.
Here is a tiny example which checks if the current user is logged-in before accessing any page, otherwise redirect on the login page. This following code is not complete (see the last reference for a better explanation) but it shows you the basics (subscription to the event, dependency injection, event redirection, ...)
<?php
namespace Drupal\my_module\EventSubscriber;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Url;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
class MyCustomSubscriber implements EventSubscriberInterface {
/**
* The current route match.
*
* #var \Drupal\Core\Routing\RouteMatchInterface
*/
protected $routeMatch;
/**
* Class constructor.
*
* #param \Drupal\Core\Routing\RouteMatchInterface $route_match
* The current route match.
*/
public function __construct(RouteMatchInterface $route_match) {
$this->routeMatch = $route_match;
}
/**
* {#inheritdoc}
*/
static function getSubscribedEvents() {
$events[KernelEvents::REQUEST][] = ['isLoggedIn'];
return $events;
}
/**
* It verify the page is requested by a logged in user, otherwise prevent access.
*
* #param \Symfony\Component\HttpKernel\Event\GetResponseEvent $event
* A response for a request.
*/
public function isLoggedIn(GetResponseEvent $event) {
$route_name = $this->routeMatch->getRouteName();
// Don't run any assertion on the login page, to prevent any loop redirect.
// If intend to be used on a production project, please #see
// https://www.lucius.digital/en/blog/drupal-8-development-always-redirect-all-logged-out-visitors-to-the-login-page for a better implementation.
if ($route_name === 'user.login') {
return;
}
if (\Drupal::currentUser()->isAnonymous()) {
$dest = Url::fromRoute('user.login')->toString();
$event->setResponse(RedirectResponse::create($dest));
}
}
}
To go further, you may read those explanations of registering event subscribers & some use case:
Responding to Events in Drupal 8
How to Register an Event Subscriber in Drupal 8
Always redirect all logged out visitors to the login page
I hope it will help you.

How to catch a form when it's submitted and forward form values to Dotmailer?

I've been asked to make a change to a Drupal 8 site (I'm not a Drupal Developer), the client would like an enquiry form linking up with Dotmailer. Within the theme I've tried to create a handler based on some information that I found online. But I don't know if it's doing anything. I've checked the logs section of Drupal and there isn't anything logged to indicate that the custom handler was actioned.
My theme is named abc-primary, and inside the theme folder I have created the file abc_primary.theme with the following contents;
<?php
use Drupal\media\Entity\Media;
use Drupal\Core\Form\FormStateInterface;
/**
* Implements hook_form_system_theme_settings_alter().
*/
function abc_primary_form_system_theme_settings_alter(&$form, \Drupal\Core\Form\FormStateInterface $form_state) {
$theme_file = drupal_get_path('theme', 'abc_primary') . '/abc_primary.theme';
$build_info = $form_state->getBuildInfo();
if (!in_array($theme_file, $build_info['files'])) {
$build_info['files'][] = $theme_file;
}
$form_state->setBuildInfo($build_info);
$form['#submit'][] = 'abc_primary_form_system_theme_settings_submit';
}
function abc_primary_form_system_theme_settings_submit(&$form, \Drupal\Core\Form\FormStateInterface $form_state) {
// TODO: Extra submission logic.
// This submit handler will be called before default submit handler for this form.
\Drupal::logger('mymodule')->notice('mymodule submit ') ;
}
Is the above correct or am I doing something wrong?
Create a new module, and define following method:
function mymodule_form_node_form_alter(&$form, FormStateInterface $form_state) {
//you may want to check your form_id by
// $form['#form_id']
$form['actions']['submit']['#submit'][] = 'mymodule_node_form_submit';
}
function mymodule_node_form_submit($form, FormStateInterface $form_state) {
// get values from form_state
//log or send to desired web service
}
Hope it helps.

Sitecore|WFFM| Custom Error Message with details| on Same Page with Form

I have a Web Form for Marketer set up done for one of my Pages.
I have Custom Submit Action written for it as shown below in the code snippet -
public class **CustomFormSubmit : ISaveAction**
{
public void Execute(ID formid, AdaptedResultList fields, params object[] data)
{
try
{
**//var returnValue= Custom Logic to save form data // returns true or false**
}
catch (Exception ex)
{
Logger.Log(ex.Message + ":" + builder, ExceptionCategory.Error);
throw;
}
}
In my above Web form - Success Mode is - SuccessMode/Redirect and I have a success Page configured for it.
My requirement in above scenario is to keep user on the same Page(with Form) if returnValue is false . (as shown in above code snippet)
Can anyone Please guide me in the above scenario as - how to keep user on the same Page with values filled in the form so that user can submit it again.
Product Details - 7.2 rev. 141226 , Web Forms for Marketers 2.4 rev.140117
To add further details -
I am not sure how can I go back to my page instead of the redirection in case if return is false in the above code snippet.
As soon the submit button is clicked the above function- Execute- gets called.
How do I go back to the Page - Do I need to override any function or customize something.
If any exception comes while saving data- then the control goes back to the same Page with all values filled by user retained -with the Save Action Failed Message which is configured in Sitecore .
So my requirement will be to go to to the form as happening in case of Exception when false comes as return value while saving data and to put customised Error Messages which might change each time, so not statically configured ,rather a dynamic one.
Thanks!
Saurabh
One option will be to redirect to the original page with the Form on.
Enable your form to populate the fields via Query String using the ReadQueryString property, via Presentation Details of the Form Renderer:
So on false of your Save Action you create a collection of query strings with the name of each Field, as it appears in the Form, followed by the User's value.
The code below will loop through all your fields and arrange them into a QueryString with its FieldName and Value;
string urlOfForm = HttpContext.Current.Request.Url.AbsolutePath;
var queryString = new StringBuilder("?");
foreach (AdaptedControlResult field in fields)
{
queryString.Append(string.Format("{0}={1}&", field.FieldName, field.Value));
}
urlOfForm = urlOfForm + queryString;
HttpContext.Current.Response.Redirect(urlOfForm);
Sitecore will then automatically populate the appropriate fields with the values, achieving your requirement.
EDIT
I have found that most Exceptions thrown will take the user back to the Form with their values populated. You can then pass in the cause of the failure to write to your CRM. See below for Example
if (submitFailed)
{
throw new Exception("The email address entered already exists in our System");
}
The complexity then comes in dynamically swapping out the Save Action Failed Message to show this Exception Message. All posts I find about custom Save Action Message state the only real approach is to redirect via your Custom Save Action to a different page showing a different message. Which is not suitable to your requirements.
I have found the pipeline Args you are going to need to patch FormSubmitFailedArgs and SubmitFailedArgs. The Former will need the following change
public FormSubmitFailedArgs(ID formID, AdaptedResultList fields, ID actionFailed, Exception ex)
: base(formID, actionFailed, ex)
{
this.Fields = fields;
this.ErrorMessage = ex.Message;
}
and the Latter will need
public SubmitFailedArgs(ID formID, ID actionFailed, string errorMessage, Exception innerException)
{
this.FormID = formID;
this.ActionFailed = actionFailed;
this.ErrorMessage = innerException.Message;
this.InnerException = innerException;
}
Location and Styling of Submit Message:
You need to find the FormRender sublayout file, this is defaulted to website\sitecore modules\Web\Web Forms for Marketers\Control\SitecoreSimpleFormAscx.ascx inside there you will find a compont called SubmitSummary this renders out the submit message so move it to where you require.
Also note it references the CssClass scfSubmitSummary this is what you will need to target to change the styling of the Message. This Answer is already REALLY long so I won't give a blow by blow how to change the styling of that class, see here for example - http://www.awareweb.com/awareblog/10-1-13-wffmguide
Pipeline Patching
I've dug in deeper, in order to use the custom Args we created for using the exception error message you will need to control the Pipeline which ultimately uses those Args, this is the processor Sitecore.Form.Core.Pipelines.FormSubmit.FormatMessage, Sitecore.Forms.Core in the <errorSubmit> Pipeline.
From my investigation it shouldn't take much effort then its a matter of patching it, you can modify if the Sitecore.Forms.config directly or use patch:instead from a config file within the App_Config/Includes folder - see here for more info.
One option would be to create a Custom Form Verification Action. You could save the data here, although it would be better to verify the data against your API here and then save the data in custom save action, simply since this seems more logical as to how WFFM was meant to function.
using Sitecore.Data;
using Sitecore.Form.Core.Controls.Data;
using Sitecore.Form.Core.Submit;
using System;
using System.Collections.Generic;
namespace Custom.WFFM
{
public class CustomVerificationStep : BaseCheckAction
{
public string FailedMessage { get; set; }
public override void Execute(ID formid, IEnumerable<ControlResult> fields)
{
// Call your API
// You have access to the fields, so you can pass them through as parameters to your if needed
bool flag = ServiceAPI.ValidateUserData(param1, param2, etc);
if (!flag)
{
throw new Exception(string.Format(this.FailedMessage ?? "There was an error while verifying the data against the service call"));
}
}
public override ActionState QueryState(ActionContext context)
{
return ActionState.DisabledSingleCall;
}
}
}
Create the corresponding Verification Action under /sitecore/system/Modules/Web Forms for Marketers/Settings/Actions/Form Verification:
You can change the error message by setting it in the Parameters field as <FailedMessage>Custom Failed Error Message</FailedMessage>.
And then add your verification step to your form:
If you need a different error message per form then you can set the error message to display from the Error Messages tab.
The user will then be returned to the same without any of the save actions being called and the form fields still filled in.

Plugin re-using Target parameter between calls

I've created and deployed a plugin for the Update event of a custom entity but it seems when multiple users update different entities within quick succession the plugin uses the first entity it receives for each call.
To investigate further I added NLog via NuGet and at the beginning of the Execute function I generate a Guid and log the entity Id and the Guid. When I look in the log I can see the same ID and Guid logged 3-4 times before both change.
What I think is happening is the code is being run for each user but using the first entities details, applying only to the first entity.
Why is this happening and how can I stop it? The problem is users are saying the plugin is erratic.
Here is my code:
public class OnUpdateClaimSection : IPlugin
{
private static Logger logger = LogManager.GetCurrentClassLogger();
private string logId = Guid.NewGuid().ToString();
public void Execute(IServiceProvider serviceProvider)
{
try
{
IPluginExecutionContext context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
IOrganizationServiceFactory serviceFactory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
IOrganizationService service = serviceFactory.CreateOrganizationService(context.UserId);
if (context.InputParameters.Contains("Target") && context.InputParameters["Target"] is Entity)
{
logger.Debug("{0} {1}|{2}|{3}", logId, context.MessageName, context.PrimaryEntityName, Common.GetSystemUserFullName(service, context.UserId));
var entity = context.InputParameters["Target"] as Entity;
logger.Debug("{0} {1}", logId, entity.Id);
var claimSection = GetClaimSection(service, entity.ToEntity<ClaimSection>());
CalculateClaimTotals(service, claimSection);
}
}
catch (Exception ex)
{
logger.Error("{0} Exception : {1}", logId, ex.Message);
throw;
}
}
}
Plugin classes are instantiated once by the CRM platform and are then reused for requests. Therefore you must be very careful when using class field variables, because they are not guaranteed to be thread-safe.
In your example field logId is modified in the Execute method. Race conditions of multiple threads are causing the effects you describe.
I suggest to only use plugin class fields when you have made sure that their implementation is absolutely thread-safe.

Writing a custom condition in WSO2 CEP with left and right argument

I want to extend the Wso2 CEP product in our needs and try to write a custom condition as indicated in this official wso2 cep link.
I am able to write an extension class that extends "org.wso2.siddhi.core.executor.conditon.AbstractGenericConditionExecutor" and implement its abstract method as indicated below:
#SiddhiExtension(namespace = "myext", function = "startswithA")
public class StringUtils extends
org.wso2.siddhi.core.executor.conditon.AbstractGenericConditionExecutor {
static Log log = LogFactory.getLog(StringUtils.class);
#Override
public boolean execute(AtomicEvent atomicEvent) {
log.error("Entered the execute method");
log.error("Atomic event to string: " + atomicEvent.toString());
return true;
}
}
when i use this extensioned method as:
from allEventsStream[myext:startswithA(name)]
insert into selectedEventsStream *;
In this situation, i want that startswithA method returns true if the name field has 'A' at the begining of it. However when i run this query in CEP the whole event drops into my execute function i.e. there is no sign to show that i send "name" field is sent to startswithA method as argument.
How can i understand which field of the stream is sent to my extended method as argument?
Also i want to write conditions like
from allEventsStream[myext:startswith('A', name)]
insert into selectedEventsStream *;
How can i achive this?
In 'AbstractGenericConditionExecutor' there's another method that gives you the set of expression executors that are included in the parameters when executor instantiates:
public void setExpressionExecutors(List<ExpressionExecutor> expressionExecutors)
You don't necessarily have to override this method and store the list, it is already stored there in the 'AbastractGenericConditionExecutor' as a list named expressionExecutors. You can pass the event to these executors to retrieve the relevant values from the event in order.
For an example, if you include a variable (like 'name') in the query (as a parameter at index 0), you'll get a 'VariableExpressionExecutor' in the list at index 0 that will fetch you the value of the variable from the event. Similarly for a constant like 'A', you'll get a different executor that will give you the value 'A' when called.
To add to Rajeev's answer, if you want to filter all the names that starts with 'A', you can override the execute method of your custom Siddhi extension similar to the following segment.
#Override
public boolean execute(AtomicEvent atomicEvent) {
if(!this.expressionExecutors.isEmpty()) {
String name = (String)this.expressionExecutors.get(0).execute(atomicEvent);
if(name.startsWith("A")) {
return true;
}
}
return false;
}
When writing the query, it would be similar to
from allEventStream[myext:startsWithA(name)]
insert into filteredStream *;
You can extend this behaviour to achieve an extension that supports
from allEventsStream[myext:startswith('A', name)]
type queries as well.
HTH,
Lasantha