# Question
Title: Using JSON:API to replicate content on external location
I would like to create content on another Drupal instance using JSON:API. Using the jsonapi\_extras service, serializer.normalizer.entity.jsonapi\_extras, entities can serialized but contain more node information than is required for remote location.

What I need to do is to prune this JSON object further to only include what is required, but haven't found an elegant way to do this with the EntityToJsonApi class. It seems like a workaround might be to request this JSON object from the HTTP API and limit included data with sparse fieldsets, but wanted to get some feedback on the best way to achieve this.

# Answer
> 1 votes
For consideration, an interesting way do this that was suggested to me by gabesullice, is to deny access to the fields. This also resulted a performance improvement in jsonapi 1.x, maybe 2.x as well.

```
/**
 * Implements hook_entity_field_access().
 */
function MYMODULE_entity_field_access($operation, $field_definition, $account, $items = NULL) {

  $request = \Drupal::request();
  if (strstr($request->getUri(), '/jsonapi/node/')) {
    $name = $field_definition->getName();
    switch ($name) {
      case 'field_blah':
      case 'uid':
      case 'moderation_state':
      case 'promote':
      case 'sticky':
      case 'unpublish':
      case 'review_date':
      case 'vid':
      case 'type':
      case 'revision_translation_affected':
      case 'revision_timestamp':
      case 'revision_log':
      case 'revision_id':
      case 'revision_uid':
      case 'langcode':
      case 'default_langcode':
        return AccessResult::forbidden();
    }
  }
  return AccessResult::neutral();
}

```

---
Tags: 8
---


title: Settings.php in read only mode?

text:
# Question
Title: Settings.php in read only mode?
When does drupal settings.php become read-only? I experienced the drupal encountered an error when I was just creating a view. Cannot see the error logs from the admin side. Thought will enable the php error reporting in settings.php and it is shown as read-only? Why does it happen? How can I find the error causing this issue?

# Answer
> When does drupal settings.php become read-only?

Towards the end of the installation process, when the install scripts have finished writing all they need to write to the file.

> Why does it happen?

Because leaving that file writeable represents a security risk.

> How can I find the error causing this issue?

It's not an error, so there's no cause to find.
> 1 votes

---
Tags: configuration
---


# Question
Title: Drupal commerce (Coupon & Discounts) - Alter rule condition
I have a drupal commerce website on which I have add the discout module to make discounts and coupons. I have in my backoffice a list of all the orders and I can modify them if I want.

The problem is the following :

If I create an order with a discount limited in the time, at the moment when I create the order the coupon is valid and it's added to my order. But then, multiple days ago I have to modify this order for some reasons, but now the coupon is expired, and if I update my order then the coupon is recalculed but it's expired so the order doesn't have the discount now.

But I need to have the possibility to add / remove coupon after an order was done. How can I fix this ?

EDIT :

I think this is this one I need to modify :

```
/**
 * Rules condition: Check discount can be applied.
 */
 function commerce_discount_date_condition($discount_name) {
   $wrapper = entity_metadata_wrapper('commerce_discount', $discount_name);
   $time = time();

   return $time >= $wrapper->commerce_discount_date->value->value() && $time <=
   $wrapper->commerce_discount_date->value2->value();
 }

```

But how can I modify it without change the module files, I don't think that this function have a hook ?

And you say that I just have to add the uid 1, but I have some other person that are admin on the site and can modify orders, I think I can add a verification on the roles ?

EDIT 2 :

The function doesn't have a hook but the two functions that call it have one :

```
function commerce_discount_commerce_discount_rule_build($rule, $discount)

```

And

```
function commerce_discount_rules_condition_info()

```

I try to use these hooks in my module (with \_alter and without) but they are not triggering, I don't know these functions maybe the rules function are called only certain times ?

# Answer
If someone need to do the same thing as me, finally I compare the dates of the coupon with the order date, so if a user use a coupon on an order, when I edit it even if the coupon is expired the order still have it.

Here is how I proceed :

First I rewrite the condition 'commerce\_discount\_date\_condition' :

```
  function mymodule_rules_condition_info_alter(&$conditions) {
      $conditions['commerce_discount_date_condition'] = array(
        'label' => t('Check discount dates'),
        'group' => t('Commerce Discount'),
        'parameter' => array(
          'commerce_discount' => array(
            'label' => t('Commerce Discount'),
            'type' => 'token',
            'options list' => 'commerce_discount_entity_list',
          ),
          'commerce_order' => array(
            'type' => 'commerce_order',
            'label' => t('Commerce order'),
            'description' => t('The order the discount would be applied to.'),
            'wrapped' => TRUE,
          )
        ),
        'base' => 'ga_registration_discount_date_condition',
      );
    }

```

I add in this condition the order on which the discount is, so I can use it in the 'base' function :

```
    function mymodule_discount_date_condition($discount_name, $order) {
      $wrapper = entity_metadata_wrapper('commerce_discount', $discount_name);
      dsm("cond");
      // Use the order time
      $time = $order->created->value();
      return $time >= $wrapper->commerce_discount_date->value->value() && $time <= $wrapper->commerce_discount_date->value2->value();
    }

```

This function use the order creation time to compare with the discount dates.

The last thing to do was to rewrite the whole commerce\_discount\_rule\_build hook (The only thing that changes are in the end) :

```
function mymodule_commerce_discount_rule_build($rule, $discount) {
  $wrapper = entity_metadata_wrapper('commerce_discount', $discount);
  $discount_offer = $wrapper->commerce_discount_offer->value();
  $wrapper_discount_offer = entity_metadata_wrapper('commerce_discount_offer', $discount_offer);

  // Check if property is attached to commerce free shipping!
  if (isset($wrapper_discount_offer->commerce_free_shipping)) {
    if (!$shipping_service = $wrapper_discount_offer->commerce_free_shipping->value()) {
      // No need to change the rules event.
      return;
    }

    // Add missing parameter.
    foreach ($rule->actions() as $action) {
      if ($action->getElementName() == 'commerce_discount_free_shipping_service') {
        $action->settings['shipping_service'] = $shipping_service;
      }
    }
  }

  // Product level discounts must pass the line item's order.
  $map = array(
    'order_discount' => 'commerce-order',
    'product_discount' => 'commerce-line-item:order',
  );

  if (isset($map[$discount->type])) {
    // Add condition for per-person usage.
    if ($wrapper->discount_usage_per_person->value()) {
      $rule->condition(
        'commerce_discount_usage_max_usage_per_person',
        array(
          'commerce_discount' => $discount->name,
          'order:select' => $map[$discount->type],
          'usage' => $wrapper->discount_usage_per_person->value(),
        )
      );
    }
    // For normal usage.
    if ($wrapper->discount_usage_limit->value()) {
      $rule->condition(
        'commerce_discount_usage_max_usage',
        array(
          'commerce_discount' => $discount->name,
          'order:select' => $map[$discount->type],
          'usage' => $wrapper->discount_usage_limit->value(),
        )
      );
    }
  }

  if (!empty($discount->commerce_discount_date)) {

    $types = commerce_discount_types();
    $type = $types[$discount->type];
    // On désactive pas les règles des discounts
    $rule->active = TRUE;

    // Ajoute la condition
    $rule->condition('commerce_discount_date_condition', array(
      'commerce_discount' => $discount->name,
      'commerce_order:select' => $type['entity type'] == 'commerce_order' ? 'commerce-order' : 'commerce-line-item:order',
    ));

  }

}

```

What i change is all the things inside the last if, I always set the rule to active (normally rules are desactivated when the discount is expired), and I add to the rule the condition with the discount name and the order. If I rewrite the whole hook it's because the `$rule->condition` add a condition, even if I add a condition with the same name it's added and not updated, so if I just use the hook without unset the base function I have the original condition and mine in the rule.

So, I unset the module only for this hook :

```
function mymodule_module_implements_alter(&$implementations, $hook)
{
  if ($hook == 'menu_alter') {
    // Move ga_registration_menu_alter() to the end of the list.
    $group = $implementations['ga_registration'];
    unset($implementations['ga_registration']);
    $implementations['ga_registration'] = $group;
  }
  if ($hook == 'commerce_discount_rule_build'){
    unset($implementations['commerce_discount']);
  }
}

```
> 1 votes

---
Tags: 7, commerce
---


# Question
Title: Changing library path with hook_library_info_alter
I am having difficulties to change the library path defined by another module in \*.libraries.yml via hook\_library\_info\_alter

Essentially I have one module defining a library component in its \*.libraries.yml like that:

```
# Font Awesome
fontawesome:
  version: 4.7.0
  css:
    component:
      /libraries/fontawesome/css/font-awesome.min.css: {minified: true }

```

How has my hook to look like to change to path to:

> /libraries/fontawesome/**web-fonts-with-css**/font-awesome.min.css

Thanks for helping me!

# Answer
> 0 votes
I pushed my patch for social icons module to add dynamic font awesome css here https://www.drupal.org/project/social\_icons/issues/2984954

This is helpful to you.

```
if (isset($libraries['font-awesome'])) {
     $library_key = key($libraries['font-awesome']['css']['theme']);
     $library_array = $libraries['font-awesome']['css']['theme'][$library_key];
     unset($libraries['font-awesome']['css']['theme'][$library_key]);
     $libraries['font-awesome']['css']['theme'][$font_awesome_url] = $library_array;
}

```

---
Tags: 8, hooks
---


# Question
Title: URL in link field adds duplicate parameters in query string when using an internal relative path
Newbie Drupal 8 user using a link field to add url's like:

/samples/products?f%5B0%5D=market%3A1

when saved makes the actual link /samples/products?f%5B0%5D=market%3A1&f%5B1%5D=market%3A1&f%5B2%5D=market%3A1

It's really /samples/products?f\[0\]=market:1 encoded so maybe the link field doesn't like this? Adding links like this in other places like in wysiwyg editors work fine it seems that the link field is adding two more times.

Any ideas on where that might be happening? Could there be a bug in link module or is there an issue with using internal url's this way? Any help would be greatly appreciated. Thanks!

# Answer
> 0 votes
Seems like a bug in Core at the moment. Follow the issue queue: https://www.drupal.org/project/drupal/issues/2885351

Seems like a workaround currently is to use full paths instead of relative ones or use the patch in the issue above to patch core.

There seems to be a module someone made too since patching core isn't ideal: https://www.drupal.org/project/link\_formatter\_query\_fix

---
Tags: theming
---


# Question
Title: How do I get the node type label from a node object?
I'm trying to get the bundle label (node type label) of the node from a node object in Drupal 8.

This works, but seems long-winded:

```
$node_type = \Drupal::entityManager()->getStorage('node_type')->load($node->getType())->get('name');

```

Is there another way to get the bundle label, possible in a *less complicated* way?

# Answer
> 21 votes
The following code works, as pointed out from @Clive.

```
$type_name = $node->type->entity->label();

```

# Answer
> 14 votes
In case you need the node type machine name in Twig template:

```
{{ node.getType }}

```

# Answer
> 10 votes
If you want the label in twig

```
{{ node.type.entity.label }}

```

# Answer
> 7 votes
Try this code.

```
use Drupal\node\Entity\NodeType;

$types = NodeType::loadMultiple();
$bundle = $node->bundle();
$type_name = $types[$bundle]->label();

```

# Answer
> 2 votes
There is a function for that

```
node_get_type_label($node);

```

---
Tags: 8, entities
---


# Question
Title: Set entity reference revision field value?
How do I set entity reference revision value for a field? I have two fields, field\_test\_entity\_reference and field\_test\_entity\_revision. field\_test\_entity\_reference is of field type entity reference and field\_test\_entity\_revision is of type entity reference revision. Both are referring to the same content type. I have created a node of this content type and its id is 7747. I am trying the below code and it sets value correctly for the field which is of type entity reference but not for entity revision version.

```
$node-field_test_entity_reference->target_id=7747;
$node-field_test_entity_revision->target_id=7747;
$node->save();

```

# Answer
You need to store also the revision id, see Programmatically append multiple paragraphs to entity reference field on node.

Or simply set an entity, from which the field can get the data it needs in both cases:

```
$node->field_test_entity_reference->entity = $target_node;
$node->field_test_entity_revision->entity = $target_node;
$node->save();

```
> 2 votes

---
Tags: 8, entities, nodes
---


# Question
Title: How do I change the field value of multiple nodes?
I'm working on a Drupal project where users can post classified ads. These are simply nodes created using a custom content type.

After 3 months, we want the user who created the node to receive a reminder email. We also need this email to be sent only once.

We're using Rules to send the emails.

This seems simple enough, but the implementation has been very difficult.

Right now, I'm stuck at limiting the number of times the email is sent. I have spent considerable time and effort online trying to find the answer. I still haven't found a straightforward explanation for this task. The closest I've seen is on this forum, which is why I'm asking for your help. It would be greatly appreciated.

= The setup =

There is a "Classified Ad" content type. It includes an "Email" field, and a (hidden) "Email sent?" field.

The user creates a "Classified Ad" node.

Views lists every ad that is more than 3 months old. The View has a VBO field. It also lists the "Email sent?" and "Sticky" values.

The "Send email" Rule is activated when the Cron runs. It has no conditions. It currently has three actions:

* Load a list of entity objects from a VBO View. (This loads my list of 3 month old ads.)
* Loop. (Loads the VBO list.)

-- Send HTML e-mail. (Sends email to the "Email" field value).

That works. So far so good.

My idea from here would be to add an action in the loop which would change the value of the "Email sent?" field from 0 to 1. I would then filter my View to hide all nodes that have this value.

= The issue =

I can't make Rules loop through my View and change the field value. I know others have had comparable problems and I've tried to apply the suggested answers but I didn't have any success.

I have also tried changing the Sticky value, but no success there either. I *can* change the first VBO item (at least for some tests), but I can't make Rules loop through all the items to update my nodes.

I have tried various approaches. The latest and (it seems) most promising is to use the Views Rules module. I installed it, created my "Rules" display for my VBO list, and added the Views loop to my Rule.

From there I tested all the combinations of Actions and Data Selectors I could think of to change the field value, but still no success. There were many tests.

Adding a "Set a data value" action inside my Views Rule Loop using "field-email-sent" as Data selector seems the most logical, but then Rules asks for a second Data selector, which makes no sense. When switching to direct input mode, I have to pick a "Node identifier", which makes even less sense. Attempts with other Actions or Data selectors have yielded similar results.

So either my approach is wrong, or one/more of my parameters are wrong. Either way, I need help.

How do I make this work?

# Answer
> 1 votes
You could use the Flag module to get this to work, by configuring an appropriate (non-global) flag, which you should then use to (automagically) flag a user after such user was emailed for the first time. To actually flag such users, you should just improve the Rule you use to send those emails, by adding a rules action to flag that user with the flag you created.

Any time your existing rule is about to send another email, you can then verify if it is the very first time, or yet another time. For this verification, all you'd have to do is use a Rules Condition to check "has this user yes or no been flagged already (with the flag you created for this purpose)?".

If you're not familiar with the Flag module, checkout either of these:

PS: I'm assuming this is about D7 ...

# Answer
> 0 votes
The Flag module was indeed the perfect solution.

Flagging users would not have worked, since users can post multiple ads.

However, flagging the node works great. Very easy to set up in Rules also.

I then added a relationship in my view to the flag, which allowed me to add a "flagged" filter. Now only unflagged items will receive the email, which is what the client needed.

Thanks again!

---

P.s.: Using Flags was a much better way to go in this case, but I'm still not sure if it's possible to loop through a VBO list and change a field value. The question seems to come up from time to time, so if anyone has an answer it could be useful to post it here for future users.

---
Tags: rules, views
---


title: How do I setup Drupal with EC2 scaling?

text:
# Question
Title: How do I setup Drupal with EC2 scaling?
I want to scale my Drupal site and I was wondering how to approach it with Drupal and Amazon EC2. This is a bit related to How to use Drupal in the cloud with on demand instances.

With Amazon, I can monitor my server and if certain metrics are hit then it can launch a new instance that is load-balanced. My questions are then:

* Does Drupal create specific files as users do stuff? Is everything on the database? (I have a separate database server that all my nodes point to.)
* Are sessions local or is it on the database as well? I'm thinking of situations where user connects to server A and then gets moved to server B. Would the user lose anything?

If it helps, I'm using Drupal 6.

# Answer
> 2 votes
* Drupal core code creates files only when a user uploads a file. With third-party modules, files could be created in other cases. For example, if you are using Apache Solr Search Integration some files could be created; if you are using a module that implements an alternative caching system, some files could be created as well.
* sess\_write() saves the session ID in the "{sessions}" table, together the value of `$user->cache`, the IP address used by the user to connect, and the current timestamp.

```
db_query("UPDATE {sessions} SET uid = %d, cache = %d, hostname = '%s', session = '%s', timestamp = %d WHERE sid = '%s'", $user->uid, isset($user->cache) ? $user->cache : '', ip_address(), $value, time(), $key);
if (db_affected_rows()) {
  // Last access time is updated no more frequently than once every 180 seconds.
  // This reduces contention in the users table.
  if ($user->uid && time() - $user->access > variable_get('session_write_interval', 180)) {
    db_query("UPDATE {users} SET access = %d WHERE uid = %d", time(), $user->uid);
  }
}

```

# Answer
> 1 votes
In 2018, Bitnami is a good option for installing Drupal 8 on AWS EC2 with Debian Linux & a LAMP stack with all of the required components. Use Drush & Composer to manage the CMS, Git for version control. It is required to use AWS RDS with Elastic Beanstalk as the load balancer for the database synchronization when using MySQL or MariaDB in cluster server configurations.

---
Tags: 6, files, performance
---


# Question
Title: Override one line in core tabelselect jQuery behavior so custom checkboxes aren't affected
I have created a custom table using a Form API tableselect element, and within my table, I have embedded a checkbox form element that is checked based on an entity field (see here for details). The checkbox functionality works fine. The problem I've discovered, though, is that the `Select/Unselect All` checkbox that is created as the first column in the table also affects my custom checkboxes, in that when I click on the select/unselect all checkbox, it also selects/unselects all the checkboxes in my column.

The offending code is in the tableselect jQuery behavior, and it's one line that makes the assumption that the only checkbox a tableselect element will ever have is the one that is part of the element:

```
if ($(event.target).is('input:checkbox')) {
  // Loop through all checkboxes and set their state to the select all checkbox' state.
  checkboxes.each(function () {
    this.checked = event.target.checked;
    // Either add or remove the selected class based on the state of the check all checkbox.
    $(this).closest('tr').toggleClass('selected', this.checked);
  });
  // Update the title and the state of the check all box.
  updateSelectAll(event.target.checked);
}

```

As the comment says, it does all checkboxes. Sigh.

From what I've been reading, my only option is to use hook\_js\_alter to replace the behavior with my own version. However, that seems like a bit of overkill just to change a couple lines. Is there another way to keep this behavior from affecting my checkbox field, or am I understanding correctly?

# Answer
> 0 votes
I ended up doing what I suggested, and overriding with my own custom version of `tableselect.js`

```
/**
 * Implements hook_js_alter().
 */
function language_dashboard_js_alter(&$javascript) {
  // Override core tableselect.js with custom version.
  $javascript['misc/tableselect.js']['data'] = drupal_get_path('module', 'language_dashboard') . '/js/language_dashboard_tableselect.js';
}

```

just to tweak this one line from this:

```
checkboxes = $('td input:checkbox:enabled', table).click(function (e)

```

to this:

```
checkboxes = $('td input:checkbox:enabled', table).not("input[id*='staging_only']").click(function (e)

```

by adding the `.not()` selector. It seems like a lot of code copying just for that one modification, but as of now, I don't see any other way.

---
Tags: 7, forms, javascript
---


# Question
Title: Remove <div class="form-item"> around form items
I want to use as much as I can from http://twitter.github.com/bootstrap/base-css.html#forms for my theme, and as part of this I really want to get rid of the `<div class="form-item">` that is being wrapped around each `<input>` element.

As far as I can tell the only way to do this is by copying the entire http://api.drupal.org/api/drupal/includes!form.inc/function/theme\_form\_element/7 theme\_form\_element() function and remove the code that produces the `<div>` wrapper.

Is this really the only way?

# Answer
Yes. These classes are hardcoded into Drupal and are not only used for visible styling but also for the jQuery code that "animates" the forms (validation, autofill, auto focus, etc.)

HTML elements can have multiple classes. You could just add the classes to the Drupal form and then create your own theme. By creating your own theme you could not theme the default Drupal classes and instead just theme the classes from Base CSS.
> 2 votes

# Answer
I found that just adding:

```
'#theme_wrappers' => array(),

```

to the form item removed the DIVs.
> 2 votes

# Answer
1. Implement hook\_theme

```

/**
 * Implements hook_theme().
 */
 function mytheme_theme($existing, $type, $theme, $path)
 {
     return array(
         'mywrapper' => array(
             'render element'  => 'element',
         ),
     );
 }

```
2. Alter the form using hook (or wherever you're accessing the form)

```

 /**
  * Implements hook_form_FORMID_alter().
  */
 function mytheme_form_formid_alter(&$form, &$form_state)
 {
      // You could wrap the element using this
      $form['quantity']['#prefix'] = 'div class="custom_class_outer">';
      $form['quantity']['#suffix'] = '/div>';

      // And clear the other wrappers
      $form['quantity']['#theme_wrappers'] = array();

      // Or we could use wrapper
      $form['quantity']['#theme_wrappers'] = array('mywrapper');
 }

```
3. Implement wrapping function

```

 /**
  * Theme wrapper
  */
 function mytheme_mywrapper(&$vars)
 {
     return 'div class="custom_class_inner">' .
           $vars['element']['#children'] . '/div>';
 }

```
4. drush cc all
> 0 votes

---
Tags: 7, theming, forms
---


title: How to use libraries function

text:
# Question
Title: How to use libraries function
I have the libraries module to add some libraries in my drupal site. Currently I need to use mailjet, I found some modules with some functions to talk with api but I've tested this for multiple hours and can't make this works (functions doesn't works if I don't modify them).

So I want to use the official php library for MailJet, I download it and put it in the sites/all/libraries folder.

In my module I try to call this librarie but doesn't work :

Here a try with libraries\_detect :

```
 $name = 'mailjet';
 if (($library = libraries_detect($name)) && !empty($library['installed']))
 {
   // The library is installed. Awesome!
   dsm("installed");
 }
 else {
  // Something went wrong. :déçu:
  // This contains a short status code of what went wrong, such as 'not found'.
  $error = $library['error'];
  // This contains a detailed (localized) error message.
  $error_message = $library['error message'];
 }

```

Always pass in the else, and here with libraries\_load :

```
if (($library = libraries_load('mailjet')) && !empty($library['loaded'])) {
  // Do something with the library here.
  dsm("Loaded");
} else {
  dsm('not loaded');
}

```

Same, always in the else.

What am I doing wrong ?

# Answer
> 1 votes
After you install and libraries Module, and put `MailJet` Library to *sites/all/libraries* make sure that your structure like:

```

sites/all/libraries/mailjet/src/Mailjet/Client.php
sites/all/libraries/mailjet/src/Mailjet/Config.php
sites/all/libraries/mailjet/src/Mailjet/Request.php
sites/all/libraries/mailjet/src/Mailjet/Resources.php
sites/all/libraries/mailjet/src/Mailjet/Response.php

```

And then implement `hook_libraries_info` like the following:

```
/**
 * Implements hook_libraries_info().
 */
function myModule_libraries_info() {
  $libraries['mailjet'] = array(
    'name'              => 'mailjet',
    'vendor url'        => 'https://github.com/mailjet/mailjet-apiv3-php',
    'download url'      => 'https://github.com/mailjet/mailjet-apiv3-php/archive/master.zip',
     // version arguments it's important, mailjet libary version can be found in Config.php file in line 19.
    'version arguments' => array(
      'file'    => 'src/Mailjet/Config.php',
      'pattern' => '/v+(\d+\.\d+\.\d+)/',
      'lines'   => 19,
    ),
    'files'             => array(
      'php' => array(
        'src/Mailjet/Client.php',
        'src/Mailjet/Config.php',
        'src/Mailjet/Request.php',
        'src/Mailjet/Resources.php',
        'src/Mailjet/Response.php',
      ),
    ),
  );
  return $libraries;
}

```

Now you can load your library like

```
$library = libraries_load('mailjet');
  if ($library['loaded']) {
    dsm("Loaded");
  }
  else {
    dsm('not loaded');
  }

```

---
Tags: 7
---


# Question
Title: Is it possible to build a brand new theme without using Twig?
I'd like to move to Drupal 8 for my next project. I understand that, from version 8, Drupal standard template engine is Twig.

Is it possible to build a brand new Drupal 8 theme without using Twig at all?

# Answer
Consider using React, Angular, Vue, or Gatsby as an application development framework with Node.js & use GraphQL for the Drupal 8 integration with other APIs, i.e. a decoupled or headless Drupal CMS solution. You may still need to use a Twig theme for the D8 administration section, although there are also projects in development for that.
> 1 votes

---
Tags: 8
---


# Question
Title: CKEditor is missing when editing a page, but works fine when creating a new page
On our drupal 7 site, we have CKEditor installed. It works fine when creating new content, but it doesn't activate properly when you go to edit the same page later. It acts like there is no editor installed and just gives a standard textbox with source.

If I click "Edit Summary", it will usually show up for the summary textbox (though not always).

Why isn't CKEditor showing up for the Body textbox?

Note that I am not selecting "switch to plain text editor". If I do, and if I see CKEditor on "Edit Summary", then clicking "switch to plain text editor" does properly hide the CKEditor for the Summary textfield. Clicking "switch to rich text editor" also restores CKEditor for the Summary, but the main Body textbox still acts like it uses a plain text editor.

## New Page - CKEditor Showing

---

## Edit Page - CKEditor Missing

---

## Edit Page Summary - CKEditor Showing (but still missing for the body textbox - not shown in screenshot)

---

**Edit**: More details on the exact problem I found that the reason a New Page loads ckeditor, while editing does not, is because the textbox is empty. If I add text to the new page, switch to 'plain text editor', then try to switch back to 'rich text editor', then CKEditor does not load.

Similarly, if I clear out the data in the textbox while editing a page, and switch text editors, CKEditor loads up.

There is a 500 error being returned, causing CKEditor to not load. When there is text, switching to 'rich text editor' causes an AJAX call to the server to validate or otherwise process the textbox data. Since it fails, CKEditor never loads.

But if there is nothing in the textbox, the AJAX call never happens (and thus no 500 is returned), so CKEditor loads up just fine. This is also why CKEditor loads in the Summary: it has no data.

# Answer
The solution, in my situation, was two-fold.

## Multiple CKEditor versions in different locations

First, the main reason was due to having multiple versions of CKEditor installed in different locations.

When you install the CKEditor module, you must also download the actual CKEditor code separately. It can be installed within the modules/ckeditor folder or in libraries. I had updated the module as well as the code, but the old version persisted in the libraries folder. Sometimes CKEditor would load, and other times it wouldn't. Likely it was due to one CKEditor library loading first.

Removing all duplicate references and clearing the cache worked. CKEditor loaded again on most of our sub-sites.

## Buggy code on server causing a 500 error

The second problem was due to faulty php code on one of our subsites. For some reason, an unrelated page included other PHP and was somehow being called when CKEditor made the AJAX call to the server. The server responded with a 500 error, and the error logs shows that this unrelated page was somehow being called and failing.

I took out the reference to the buggy code, and CKEditor worked again for that page.

I still have no idea how that code was being called. It was a simple page with a single line of php, including a different file. Yet CKEditor failed to load on *every* page within the subsite, not just that page. I chalk it up to lacking enough understanding of Drupal internals.
> 4 votes

# Answer
I had the same issue, ckeditor worked when creating new content but not when editing.

Doing a code inspect showed couple of chosen module errors (7.x-2.0-beta4)

Failed to load resource: the server responded with a status of 404 (Not Found) http://example.com/sites/all/libraries/chosen/chosen.jquery.min.js?v=1.1.0

Attr.specified is deprecated. Its value is always true. jquery.js?v=1.4.4:52

Uncaught TypeError: undefined is not a function chosen.js?v=1.1.0:60

My issue was I had extracted the Chosen.tar.gz file which doesnt have the .js file, you need the chosen.zip code in your libraries.
> 2 votes

# Answer
Another thing that might cause this behaviour is having php code in the nodes, for example redirects performed with php code in the body field. If this is the case, you should use an external redirects module, because using php redirects in node fields will only cause trouble.

The issue seems to be also active in the module's issues queue.
> 2 votes

# Answer
I resolved this using the following;

I initially had the *Default* and *Alternate* jQuery settings @ `1.10` and no *CKEditor* in Full or Filtered.

Drupal `7.32`

Bootstrap `7.x-3.0`

CKEditor Module `7.x-1.16`

* No special settings

jQuery update Module `7.x-2.4`

* Default jQuery version `1.8`
* Alternate jQuery version `1.8`
* jQuery and jQuery UI CDN = jQuery, google or MS

Dropping to 1.8 was the trick. shrug ... I don't know where to find the version I've got installed. :-)

Granitize me!
> 1 votes

# Answer
I fixed this issue by following bellow steps:

Go to `Content type -> Basic page -> Manage fields -> body`

Now select "`Plain Text`" under "`Text processing`" then save. Again I changed from "`Plain text`" to "`Filtered Text`". Now its working.

I think here main concept is just clicking "`Save`" button
> 1 votes

# Answer
Thanks for this. I had the same problem where my editor wasn't showing up on the node forms and neither was my chosen. Looking in firebug console I could see JS errors relating to some JS I was adding in with the following hook:

```
function module_page_build($page) {
  $node = menu_get_object();
  if ($node) {
    drupal_add_js(drupal_get_path('module', 'module') .'/some-js.js');
  }
}

```

My check for $node is obviously not enough as this is true on the node edit forms. My solution will be to add some extra checks here. Maybe just using is\_admin().
> 1 votes

# Answer
I had this happen on some nodes but not others; the problem was a 403 error being triggered on some pages with the ajax call to /ckeditor/xss ... mod\_security was throwing the 403 error because of the content in the post. In my particular case, it was because of javascript snippets in the page content (widget loaders and sizers, etc), but it can be triggered by other things.

As a workaround, I could cut the content from the field, toggle to WYSIWYG mode, and then CKEditor would load. Then I could toggle the "Source" button on CKEditor, and paste the code back in, then toggle back into WYSIWYG mode and edit as normal, and save. Upon editing the next time, would have to do this again.

I think one solution could be to exclude the path /ckeditor/xss from mod\_security filtering ... definitely don't disable mod\_security altogether though, except for troubleshooting this issue.

Some other causes for this issue and workarounds are discussed here: https://www.drupal.org/node/2069871 ... thanks to "75th Trombone" on drupal.org for the suggestion about mod\_security, which proved to be the cause in my case as well.
> 0 votes

---
Tags: wysiwyg
---


title: Ajax error on node form edit page

text:
# Question
Title: Ajax error on node form edit page
I'm trying to ajaxify part of node form. I have a hook\_form\_alter that adds extra ajax property to a widget of select field. The problem I'm facing is the callback method / function, which is not fired when change is happening.

So this is my form\_alter code which resides in accountancy\_invoice/accountancy\_invoice.module:

```
function accountancy_invoice_form_node_form_alter(array &$form, \Drupal\Core\Form\FormStateInterface $form_state, $form_id) {
  if ($form_id == 'node_invoice_form') {

    $form['accommodation'] = array(
      '#type' => 'container',
      '#prefix' => '<div id="user-full-name">Dave</div>',
      '#attributes' => array(
        'class' => 'user-full-name',
      ),
    );

    $form['field_invoice_customer']['widget']['#ajax'] = array(
      'callback' => 'Drupal\accountancy_invoice\Form\AjaxifyForm::customerValues',
      'progress' => array(
        'type' => 'throbber',
        'message' => NULL,
      )
    );

  }
}

```

This is my AjaxifyForm class which resides in accountancy\_invoice/src/Form/AjaxifyForm.php:

```
namespace Drupal\accountancy_invoice\Form;

use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Ajax\AjaxResponse;
use Drupal\Core\Ajax\HtmlCommand;
use Drupal\Core\Ajax\ReplaceCommand;
use Drupal\Core\Ajax\OpenModalDialogCommand;

class AjaxifyForm {
  public static function customerValues(array &$form, \Drupal\Core\Form\FormStateInterface $form_state) {
    $response = new AjaxResponse();
    $response->addCommand(new ReplaceCommand('#user-full-name', '<span class="unit">Bryan</span>'));
    $title = "Example Dialog";
    $content = "foo bar content";
    $options = array();
    $response->addCommand(new OpenModalDialogCommand($title, $content, $options));
    return $response;
  }
}

```

It doesn't really matter at the moment what this ajax should do. For testing purposes I just want to change name from Dave to Bryan in #user-full-name and display sample dialog box.

In the console, each time ajax is triggered I got an error:

This is complete error message (it's trimmed in the screenshot): ↵An AJAX HTTP error occurred.↵HTTP Result Code: 200↵Debugging information follows.↵Path: /node/add/invoice?ajax\_form=1↵StatusText: OK↵ResponseText: TypeError: Argument 1 passed to Drupal\Core\Form\FormState::setError() must be of the type array, null given, called in /var/www/drupalvm/drupal/web/core/lib/Drupal/Core/Field/WidgetBase.php on line 441 in Drupal\Core\Form\FormState-\>setError() (line 1093 of /var/www/drupalvm/drupal/web/core/lib/Drupal/Core/Form/FormState.php).

# Answer
> 0 votes
This is a generic issue that will show for various reasons, often because of a bug in the code handling the server response. Look in your server logs for more information as to what has happened.

# Answer
> -1 votes
If you expeirence with this kind of error it happens because of bug in drupal core. This is the discussion about this issue: https://www.drupal.org/node/2027059

I've applied patch from #55 and ajax requests has started to work.

---
Tags: 8, forms, ajax
---


# Question
Title: How to pass values or data from a page to a webform using webform link field?
If we have a link to a webform in a content type and we need to pass data or values to that webform from the fields available from the content type. How do we achieve this.

I'm using webform 8.x-5

I had hardly any success with 'Field Token Value' or setting default values from the available token of the webform build settings.

# Answer
> 2 votes
Add a webform reference field to the content type and put the default values in

**Default submission data (YAML)**

and you see this example provided by the field:

```
# This is an example of a comment.
element_key: 'some value'

# The below example uses a token to get the current node's title.
# Add ':clear' to the end token to return an empty value when the token is missing.
title: '[webform_submission:node:title:clear]'
# The below example uses a token to get a field value from the current node.
full_name: '[webform_submission:node:field_full_name:clear]

```

---
Tags: 8, webforms
---


# Question
Title: How do I use 2 different taxonomy view modes on one node page?
On a node page I have a term reference field. This term contains 3 fields

For the term I've created a few view modes:

1. view\_mode\_first contains: field\_1
2. view\_mode\_second contains: field\_2, field\_3

hook theme suggestions:

```
  function theme_theme_suggestions_taxonomy_term_alter(array &$suggestions, array $variables) {
    $term = $variables['elements']['#taxonomy_term'];
    array_splice($suggestions, 2, 0, 'taxonomy_term__' . $term->bundle() . '__' . $variables['elements']['#view_mode']);
  }

```

node field template, where I'm displaying referenced terms:

```
  {% for item in items %}
      {% set first = item.content|merge({'#view_mode': 'view_mode_first'}) %}
      {{ first }}
  {% endfor %}

  {% for item in items %}
      {% set second = item.content|merge({'#view_mode': 'view_mode_second'}) %}
      {{ second }}
  {% endfor %}

```

But as a result I'm getting the same templates everywhere: **taxonomy-term--name--view-mode-first.html.twig** and not a **taxonomy-term--name--view-mode-second.html.twig**

I found that if I disable render cache in a settings.local file, both templates work:

```
$settings['cache']['bins']['render'] = 'cache.backend.null';

```

Is there possibility to disable render cache for specific node and/or fields?

# Answer
The problem is that the view mode is also part of the cache keys and you would need to update them as well to avoid that both view modes are cached under the same cache id.

> Is there possibility to disable render cache for specific node and/or fields?

Yes, this is another possibility, you can disable caching of specific entities, in this case taxonomy terms, by removing the cache keys, which are in the `#cache` property. It's easier to manipulate this array in PHP, but this should also be possible in Twig. You can try to get #cache with the filter `|without('keys')` and merge it later with `first` and `second`.

You can also try to remove `#cache` completely from the second rendering

```
{{ second|without('#cache') }}

```

because all relevant cache metadata should bubble up already from the first rendering of the taxonomy term.
> 4 votes

---
Tags: theming, taxonomy-terms
---


title: Where are the Views located?

text:
# Question
Title: Where are the Views located?
This is related to this question I asked.

Bottom line is that for some reason some junk view is stuck under the Views list and when accessing the List I get a WSOD.

I confirmed that is a view issue after uninstalling and reinstalling Views the problem went away.

An easier fix might be to take a look on the saved Views. But the major question here is where are the created Views located? (in a folder? in the DB?).

I just want to see that malformed View and delete it.

EDIT: A screenshot of the output of drush vl. Note the enabled view on the top that has no name or machine name.

# Answer
> 2 votes
Active views are stored in the config table of the db. For example the view that produces the "recently added" block is stored in config table as `name=views.view.content_recent`.

The view's config itself is in the data (blob) column of that config-row.

Views that get installed by contrib modules at install time are located in the config/install/views.view.\* file of the module's folder.

Try

```
mysql> select * from config where name='views.view.content_recent';

```

---
Tags: views, 8
---


title: Cache in hook preprocess page

text:
# Question
Title: Cache in hook preprocess page
I'm trying to get a value of GET variable in `hook_preprocess_page()`, but I'm having problems with the cache. Only the first time the code is executed, I can get the value of the variable to put it in a cookie; if I try to load the same page with different values, the code doesn't execute.

I need to put this value in cookie to identify the source of leads.

# Answer
> 6 votes
If your preprocess doesn't affect the result of the page, and needs to execute even when the page is served from the page cache, you should move your logic to a Kernel Event subscriber, which replaces `hook_boot()` and `hook_exit()` from Drupal 7.

DrupalizeMe has an article on Event Subscribers, but the basic components are creating a class that implements `EventSubscriberInterface`, and then registering it as a service.

**MYMODULE/EventSubscriber/MyEventSubscriber.php**

```
class MyEventSubscriber implements EventSubscriberInterface {
  static function getSubscribedEvents() {
    $events[KernelEvents::RESPONSE][] = ['onResponse'];
    return $events;
  }
  public function onResponse(FilterResponseEvent $event) {
    $request = $event->getRequest();
    $response = $event->getResponse();

    $parameter_value = $request->query->get('parameter_name');
    $response->headers->setCookie(new Cookie('cookie_name', 'cookie_value'));
  }
}

```

**MYMODULE/MYMODULE.services.yml**

```
MYMODULE.my_event_subscriber:
  class: Drupal\MYMODULE\EventSubscriber\MyEventSubscriber
  arguments: []
  tags:
    - { name: event_subscriber }

```

The request event can modify the request before other components receive it, but cannot affect the response. The response event cannot modify the request, but is able to alter the response after it has been composed.
If you need to do both, your service object can store some state during the request event, and then alter the response based on that state during the response event.

# Answer
> 6 votes
To bust the cache in a preprocess hook in Drupal 8 you simply can add your desired cache context to the `$variables`. In the following example your code now is going to be cached per session.

```
/**
 * Implements template_preprocess_page().
 */
function MYMODULE_preprocess_page(&$variables) {

  // Your code ...
  // ...
  // ...

  // Ensure the cache varies correctly.
  $variables['#cache']['contexts'][] = 'session';
}

```

Read the docs for more available contexts: Cache contexts.

---

Alternatively you can set your own tag like answered in a similar question: Remove cache in hook\_preprocess\_node

# Answer
> 2 votes
If you want disable cache in hook\_preprocess\_hook - you can simply add cache element to variables. Example:

```
function MYMODULE_preprocess_page(&$variables) {
  // prevent caching certain element
  $variables['my_custom_element'] = [
    '#markup' => t('My message'),
    '#cache' => ['max-age' => 0],
  ];
  // or prevent caching all page
  $variables['#cache']['max-age'] = 0;
}

```

---
Tags: 8, caching
---


title: Custom validation

text:
# Question
Title: Custom validation
I have a webform with 195 different select options, each with their own form key.

I want to make the form so someone can only select 10 items, but the max number validation isn't working so I'm trying to figure out how to make this work.

I cannot group them under one form key so I'm hoping someone can help me out.

# Answer
You can use webform\_validation contribute module. As the description of the module page, It has the following options:

* Numeric values (optionally specify min and/or max value)
* Minimum length
* Maximum length
* Minimum number of words
* Maximum number of words
* Equal values on multiple fields
* Unique values on multiple fields
* Specific value
* Require at least one of two fields
* Require at least one of several fields
* **Minimum number of selections required**
* **Maximum number of selections allowed**
* **Exact number of selections required**
* Plain text (disallow tags)
* Regular expression
* Must be empty (anti-spam: Hide with CSS)
* Words blacklist
* Must match a username
> 1 votes

---
Tags: webforms
---


# Question
Title: How to create a view to count comments per user?
I am trying to create a view with a table that shows all the users and the total number of comments that user posted in descending order. I am not sure what I am doing wrong but I spent hours on this already...

Can anybody point me to the right direction?

The closest I got is I created a view of comments so far with the author name that I tried to aggregate. But I am getting all sorts of weird results.

# Answer
**I got it!** Sharing my way ;)

What I did:

1. Created a view of comments of my type.
2. As fields, I added Author uid **twice**.
3. Set aggregation (COUNT) on the second Author uid.
4. Added a sort criteria for aggregated (Count) Author uid as descending.

woala... Wasn't that hard after all :)
> 3 votes

---
Tags: views, 8, users, comments
---


# Question
Title: Show multiple term references as ID in one row
I've got a view of nodes, displayed as fields. As each node may have references to multiple terms/tags, I would like to show all these tags.

When I use field "term\_node\_tid", then I get the multiple values properly displayed in one row separated by commas. But instead of the readable term, I need the term-IDs.

So I set up another field using "tid", but now only the ID of the first term is shown. There's no multiple value field setting, where I could tweak things. Any help much appreciated.

# Answer
> 4 votes
## Change field Formatter

If you're showing the actual Content field you can change the `Formatter`. Instead of using `Label` use **Entity ID** under `Formatter`.

This will output Term IDs instead of term names, and all the Multi-valued settings will apply the same way as for the names output.

## Rewrite results with TID replacement token

Another way or changing the field output in D8 views (if the Formatter option isn't available for example) is to use the `Rewrite results > Override the output of this field with custom text` and enter the token for the TID in there. You can look up the correct token pattern under `Replacement patterns` below the `Text` area.

That is also how you would get the TID to display instead of the name if you use the field **Content: All taxonomy terms**.

---
Tags: views, 8, taxonomy-terms
---


title: Memory exhausted during csv upload?

text:
# Question
Title: Memory exhausted during csv upload?
I am uploading a csv and creating custom nodes. I have set the drupal memory limit to 512M and I can see this in the drupal status report. But when I am uploading a csv file, I am getting the error message 'Allowed memory size of 536870912 bytes exhausted (tried to allocate 20480 bytes)'. The file I am trying to upload is only 26MB. I am using batch process for creating nodes. How do I resolve this memory exhausting issue? If by increasing the memory can resolve this issue, what is the recommended memory limit I should set?

# Answer
Are you creating one node per PHP pass? If you are handling the batch API on your custom code you would need to control `$context['sandbox']`. This is an example: https://api.drupal.org/api/examples/batch\_example%21batch\_example.module/function/batch\_example\_op\_2/7.x-1.x and here another one in the context of a hook\_update: http://bleen.net/blog/running-batch-processes-update-hook-bed
> 1 votes

---
Tags: nodes, importing-exporting
---


# Question
Title: Delete from Multiple Tables using db_delete()
How can we delete fields from multiple tables using `db_delete()` and `INNER JOIN` in Drupal 7?

# Answer
You can't I'm afraid.

`db_delete()` takes a single string parameter to define which table the query is to be run against. There's no scope to provide more than one table at a time.

You can run any SQL statement you like, though, with `db_query()`, eg.

```
// Deletes all nodes and related author users...please don't actually run this!!
$sql = 'DELETE u, n FROM {node} n INNER JOIN {users} u ON u.uid = n.uid';
db_query($sql);

```
> 11 votes

# Answer
Yes, db\_delete takes a single parameter. You have to call `db_delete()` multiple times to delete data from multiple tables. Please see the following sample code:

```
/**
 * Implements hook_menu_delete().
 */
function block_menu_delete($menu) {
  db_delete('block')
    ->condition('module', 'menu')
    ->condition('delta', $menu['menu_name'])
    ->execute();
  db_delete('block_role')
    ->condition('module', 'menu')
    ->condition('delta', $menu['menu_name'])
    ->execute();
}

```

Source link:
https://github.com/drupal/drupal/blob/7.x/modules/block/block.module
https://api.drupal.org/api/drupal/modules%21block%21block.module/7.x
> 0 votes

---
Tags: 7, database
---


title: EntityReferenceSelection plugin ignored

text:
# Question
Title: EntityReferenceSelection plugin ignored
I am trying to create a simple time tracking app and for this purpose, I need an entity reference field with some additional filtering of referenced entities (only active projects can be selected).

I read some guides and it seems that what I need is to

**1. create entity reference field**

This is done in the `buildForm()` function of the `src/Form/TimesheetForm.php` class like this:

```
    $form['project'] = [
  '#type' => 'entity_autocomplete',
  '#title' => $this->t('Project'),
  '#weight' => '0',
  '#target_type' => 'project_entity',
  '#selection_handler' => 'default:active_projects',
  '#selection_settings' => [

  ],
  '#tags' => FALSE,
];

```

**2. create custom plugin**

This is done in `src/Plugin/ActiveProjectsSelection.php` like this:

```
    <?php

    namespace Drupal\timesheet\Plugin\EntityReferenceSelection;

use Drupal\node\Plugin\EntityReferenceSelection\NodeSelection;

/**
 * Provides specific access control for the node entity type.
 *
 * @EntityReferenceSelection(
 *   id = "active_projects",
 *   label = @Translation("Active projects selection"),
 *   entity_types = {"project_entity"},
 *   group = "default",
 *   weight = 3
 * )
 */

class ActiveProjectsSelection extends NodeSelection {

  /**
   * @param null $match
   * @param string $match_operator
   *
   * @return \Drupal\Core\Entity\Query\QueryInterface
   */
  protected function buildEntityQuery($match = NULL, $match_operator = 'CONTAINS') {
    \Drupal::logger('timesheet')->debug('buildEntityQuery');
    $query = parent::buildEntityQuery($match, $match_operator);
    //$query->condition('project_start_date', time(), '<');
    //$query->condition('project_end_date', time(), '>');
    return $query;
  }

}

```

However, even when I commented out all the logic, the autocomplete does not work. When I type in the field, no autocomplete popup appears. Not even the debug message is logged when I try to trigger the autocomplete.

I have also tried to extend `DefaultSelection` instead of the `NodeSelection` as I am using a custom entity which is not a `node`, but the result was the same.

It looks like my plugin is not even reached, but I do not know why.

I cleared the cache, my local site is in dev mode.

# Answer
> 1 votes
So, with help from Clive (see his comment below the original question) I have identified two issues:

1. The correct path for the `ActiveProjectsSelection.php` file should be `src/Plugin/EntityReferenceSelection/ActiveProjectsSelection.php`.
2. The id of the plugin in the annotation should be `default:active_projects`.
3. (not related to the original problem) The correct timestamp for the date comparison is `DrupalDateTime::createFromTimestamp(time())`.

With these changes, my code works perfectly. Also, it does not matter whether I extend `NodeSelection` or `DefaultSelection`, it works with either of them.

---
Tags: 8, plugins
---


title: Using JCF with dynamic forms

text:
# Question
Title: Using JCF with dynamic forms
I want to use Javascript Custom Forms (JCF) to get more attractive and consistent looking forms on my Drupal 7 site. I've got it working to a degree, however when the form changes with AJAX, the new form element does not have the JCF styling applied.

(E.g. if you have a form file upload, and you press the 'Remove' button, the form changes, without reloading the page, replacing the Remove button with the Browse and Upload buttons. But the JCF styling is not applied to these new buttons.)

This is what I have so far...

In the theme .info file:

```
stylesheets[all][] = jcf/dist/css/theme-minimal/jcf.css
scripts[] = jcf/dist/js/jcf.js
scripts[] = jcf/dist/js/jcf.file.js
scripts[] = jcf/dist/js/jcf.radio.js
scripts[] = jcf/dist/js/jcf.button.js
scripts[] = jcf/dist/js/jcf.select.js
scripts[] = jcf/dist/js/jcf.checkbox.js

```

In the theme .js file:

```
(function ($) {
    $(document).ready(function(){
        jcf.replaceAll();
     });
}(jQuery));

```

I need to call when the forms changes...

```
jcf.refreshAll();

```

I've read up about behaviours, but it seems to be all about creating your own module. Is there a simple way to call the above when the form changes?

# Answer
> 1 votes
You need a behaviour:

```
Drupal.behaviors.myBehavior = {
  attach: function(context, settings) {
    jcf.refreshAll();
  }
};

```

That will be called on DOM ready, and every time `Drupal.attachBehaviors` is invoked (i.e. after AJAX calls)

---
Tags: 7, forms, javascript, ajax
---


# Question
Title: Programmatically node update while saving node
How can I update the value of the field in the nodes while save/update the node? When you save/update the node value from the field was taken and save in the same field in the other nodes. I tried to do it hook\_ENTITY\_TYPE\_presave()

```
$nodes = Node::loadMultiple($nids);
foreach ($nodes as $node) {
  $node->set('name_field', $value);
  $node->save();
}

```

But this causes infinite recursion

# Answer
You can use hook\_entity\_presave

```
function hook_entity_presave(Drupal\Core\Entity\EntityInterface $entity) {

    if ($entity->getEntityTypeId()=='node'){
        $value="099545";
        $entity->set('name_field', $value);
    }
}

```
> 2 votes

# Answer
Your code should be called only for the current node and only once. If you avoid calling your code snippet when its for the other nodes then you won't have any problem.

Since you are in presave hook You can wrap your code with the condition to be sure you only run the code for one node (Which suppose to contain the reference - $nids ) not for each node you want to change.

The condition can be the node type if the node you loaded has a different type from the nodes you want to update. I wrote example code how you can avoid that, I assume '$nids'contain the nodes ID's you want to update without the main node id which contains the reference for other nodes.

You can also avoid the whole loop by adding a small condition to be sure no recursion happened.

```
    if (!in_array($entity->id(), $nids){
      $nodes = Node::loadMultiple($nids);
      foreach ($nodes as $node) {
        $node->set('name_field', $value);
        $node->save();
      }
    }

```

Also another suggestion, you can add a custom submit handler for node add/edit form and you can do whatever you want in your handler create/update other nodes.
> 0 votes

# Answer
You can use hook\_ENTITY\_TYPE\_presave

```
use Drupal\node\NodeInterface;

function mymodule_node_presave(NodeInterface $node) {
  $node->set('name_field', $value');
}

```

Here no need to load node.
> 0 votes

# Answer
If that is the code executed in `hook_ENTITY_TYPE_presave()` there are two errors.

* The parameter given to the hook is a single entity; it doesn't get an array of entity IDs
* A `hook_ENTITY_TYPE_presave()` implementation cannot save any entity of the type it handles, since that would cause that hook to be invoked again, and since the hook saves an entity, it would cause an infinite loop

If you are updating nodes that are associated in someway to the updated one, it's better to implement `hook_cron()` to check those nodes that have been saved, and change the associated nodes.

Otherwise, if you are just trying to update the node that has been updated, you just need to use the following code.

```
use Drupal\node\NodeInterface;

function mymodule_node_presave(NodeInterface $node) {
  $node->set('name_field', $value');
}

```

Don't call `$node->save()`, which would cause the infinite loop, and it would not make sense, since the entity is going to be saved. (The hook is invoked before the entity is saved.)
> 0 votes

---
Tags: 8, nodes
---


title: How can I clear the Varnish cache?

text:
# Question
Title: How can I clear the Varnish cache?
We have installed node queue module on Pressflow 6 + Varnish. For clearing the varnish cache for node queues, we developed rules using the Rules, and Cache action modules. Whenever we update the content, it is reflecting for logged in users, but it is not reflecting for anonymous users.

Could you please suggest us for how to clear varnish cache with rules or any custom code?

# Answer
The Cache Expiration module is designed to clear items from caches, including Varnish.

> This module acts as a grid to detect and act upon events that will expire URLs from caches like reverse proxy caches. The Cache Expiration module is a spinoff of the expiration code of the Boost expiration code so other modules can use it. This module stands on its own and does not use Boost in any way.
> 3 votes

# Answer
You may install Varnish HTTP Accelerator Integration module and set the following lines in your settings file:

```
/**
 * Varnishd Configuration
 *
 * This cache implementation can be used together with Varnish. You can't really use it to store or get any values, but you can use it to purge your caches.
 * This cache implementation should ONLY be used for cache_page and no other cache bin!
 */
if (class_exists('VarnishCache')) {
  // Add Varnish as the page cache handler.
  $conf['cache_backends'][] = 'sites/all/modules/contrib/varnish/varnish.cache.inc';
  // Drupal 7 does not cache pages when we invoke hooks during bootstrap. This needs to be disabled.
  $conf['page_cache_invoke_hooks'] = FALSE;
  $conf['cache_class_cache_page'] = 'VarnishCache';
}

```

Then if module is enabled, standard clear cache should invoke clear caching for Varnish.

You may also call purge directly via calling: `varnish_purge_all_pages();`

---

Alternatively you can use curl, ie.

```
curl -X PURGE -H "Host: domain.com" http://127.0.0.1/test.html

```

Also check:
> 3 votes

# Answer
For running Drupal 8 websites using Varnish Cache, install the Purge module. Purge will manage the Varnish Cache from Drupal 8 admin unless you need to support Cache Tags.

To Support Cache Tags with Varnish, install the Varnish Purger module. Visit this GitLab page for an example of the Varnish VCL code required to support the Purge and Varnish Purge modules:

```
vcl 4.0;
import std;
import directors;

# This Varnish VCL has been adapted from the Four Kitchens VCL for Varnish 3.
# This VCL is for using cache tags with drupal 8. Minor chages of VCL provided by Jeff Geerling.

# Default backend definition. Points to Apache, normally.
# Apache is in this config on port 80.
backend default {
    .host = "127.0.0.1";
    .port = "80";
    .first_byte_timeout = 300s;
}

# Access control list for PURGE requests.
# Here you need to put the IP address of your web server
acl purge {
    "127.0.0.1";
}

# Respond to incoming requests.
sub vcl_recv {
    # Add an X-Forwarded-For header with the client IP address.
    if (req.restarts == 0) {
        if (req.http.X-Forwarded-For) {
            set req.http.X-Forwarded-For = req.http.X-Forwarded-For + ", " + client.ip;
        }
        else {
            set req.http.X-Forwarded-For = client.ip;
        }
    }

    # Only allow PURGE requests from IP addresses in the 'purge' ACL.
    if (req.method == "PURGE") {
        if (!client.ip ~ purge) {
            return (synth(405, "Not allowed."));
        }
        return (hash);
    }

    # Only allow BAN requests from IP addresses in the 'purge' ACL.
    if (req.method == "BAN") {
        # Same ACL check as above:
        if (!client.ip ~ purge) {
            return (synth(403, "Not allowed."));
        }

        # Logic for the ban, using the Cache-Tags header. For more info
        # see https://github.com/geerlingguy/drupal-vm/issues/397.
        if (req.http.Cache-Tags) {
            ban("obj.http.Cache-Tags ~ " + req.http.Cache-Tags);
        }
        else {
            return (synth(403, "Cache-Tags header missing."));
        }

        # Throw a synthetic page so the request won't go to the backend.
        return (synth(200, "Ban added."));
    }

    # Only cache GET and HEAD requests (pass through POST requests).
    if (req.method != "GET" && req.method != "HEAD") {
        return (pass);
    }

    # Pass through any administrative or AJAX-related paths.
    if (req.url ~ "^/status\.php$" ||
        req.url ~ "^/update\.php$" ||
        req.url ~ "^/admin$" ||
        req.url ~ "^/admin/.*$" ||
        req.url ~ "^/flag/.*$" ||
        req.url ~ "^.*/ajax/.*$" ||
        req.url ~ "^.*/ahah/.*$") {
           return (pass);
    }

    # Removing cookies for static content so Varnish caches these files.
    if (req.url ~ "(?i)\.(pdf|asc|dat|txt|doc|xls|ppt|tgz|csv|png|gif|jpeg|jpg|ico|swf|css|js)(\?.*)?$") {
        unset req.http.Cookie;
    }

    # Remove all cookies that Drupal doesn't need to know about. We explicitly
    # list the ones that Drupal does need, the SESS and NO_CACHE. If, after
    # running this code we find that either of these two cookies remains, we
    # will pass as the page cannot be cached.
    if (req.http.Cookie) {
        # 1. Append a semi-colon to the front of the cookie string.
        # 2. Remove all spaces that appear after semi-colons.
        # 3. Match the cookies we want to keep, adding the space we removed
        #    previously back. (\1) is first matching group in the regsuball.
        # 4. Remove all other cookies, identifying them by the fact that they have
        #    no space after the preceding semi-colon.
        # 5. Remove all spaces and semi-colons from the beginning and end of the
        #    cookie string.
        set req.http.Cookie = ";" + req.http.Cookie;
        set req.http.Cookie = regsuball(req.http.Cookie, "; +", ";");
        set req.http.Cookie = regsuball(req.http.Cookie, ";(SESS[a-z0-9]+|SSESS[a-z0-9]+|NO_CACHE)=", "; \1=");
        set req.http.Cookie = regsuball(req.http.Cookie, ";[^ ][^;]*", "");
        set req.http.Cookie = regsuball(req.http.Cookie, "^[; ]+|[; ]+$", "");

        if (req.http.Cookie == "") {
            # If there are no remaining cookies, remove the cookie header. If there
            # aren't any cookie headers, Varnish's default behavior will be to cache
            # the page.
            unset req.http.Cookie;
        }
        else {
            # If there is any cookies left (a session or NO_CACHE cookie), do not
            # cache the page. Pass it on to Apache directly.
            return (pass);
        }
    }
}

# Set a header to track a cache HITs and MISSes.
sub vcl_deliver {
    # Remove ban-lurker friendly custom headers when delivering to client.
    unset resp.http.X-Url;
    unset resp.http.X-Host;
    # Comment these for easier Drupal cache tag debugging in development.
    unset resp.http.Cache-Tags;
    unset resp.http.X-Drupal-Cache-Contexts;

    if (obj.hits > 0) {
        set resp.http.Cache-Tags = "HIT";
    }
    else {
        set resp.http.Cache-Tags = "MISS";
    }
}

# Instruct Varnish what to do in the case of certain backend responses (beresp).
sub vcl_backend_response {
    # Set ban-lurker friendly custom headers.
    set beresp.http.X-Url = bereq.url;
    set beresp.http.X-Host = bereq.http.host;

    # Cache 404s, 301s, at 500s with a short lifetime to protect the backend.
    if (beresp.status == 404 || beresp.status == 301 || beresp.status == 500) {
        set beresp.ttl = 10m;
    }

    # Don't allow static files to set cookies.
    # (?i) denotes case insensitive in PCRE (perl compatible regular expressions).
    # This list of extensions appears twice, once here and again in vcl_recv so
    # make sure you edit both and keep them equal.
    if (bereq.url ~ "(?i)\.(pdf|asc|dat|txt|doc|xls|ppt|tgz|csv|png|gif|jpeg|jpg|ico|swf|css|js)(\?.*)?$") {
        unset beresp.http.set-cookie;
    }

    # Allow items to remain in cache up to 6 hours past their cache expiration.
    set beresp.grace = 6h;
}

```

**Note:** This code is placed in the default.vcl for Varnish Cache & will enable support for Cache Tags in Drupal 8 with Purge.
> 2 votes

# Answer
I found a PHP example of how you can do it from a blog post:

```
$url = $_POST["url"];
$host = $_POST["host"];

$ip = "127.0.0.1";
$port = "80";

$timeout = 1;
$verbose = 1;

# inits
$sock = fsockopen ($ip,$port,$errno, $errstr,$timeout);
if (!$sock) { echo "connections failed $errno $errstr"; exit; }

if ( !($url || $host) ) { echo "No params"; exit; }

stream_set_timeout($sock,$timeout);

$pcommand = "purge";
# Send command
$pcommand .= ".hash $url#$host#";

put ($pcommand);
put ("quit");

fclose ($sock);

function readit() {
  global $sock,$verbose;
  if (!$verbose) { return; }
  while ($sockstr = fgets($sock,1024)) {
    $str .= "rcv: " . $sockstr . "<br>";
  }
  if ($verbose) { echo "$str\n"; }
}

function put($str) {
  global $sock,$verbose;
  fwrite ($sock, $str . "\r\n");
  if ($verbose) { echo "send: $str <br>\n"; }
  readit();
}

```
> 1 votes

---
Tags: 6, proxies
---


# Question
Title: How to override a function inside of a Trait?
My goal is to override the function isApprovalRequired() in a Trait for the module Social Auth (social\_auth). The file:

```
modules/contrib/social_auth/src/SettingsTrait.php

```

has a function (along with variables and other functions):

```
namespace Drupal\social_auth;

use Drupal\Core\Url;
use Drupal\user\UserInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;

/**
* Helper methods for Social Auth and Drupal settings.
*/
trait SettingsTrait {
  ...
  protected $configFactory;
  ...
  protected function isApprovalRequired() {

```

that I would like to overwrite. How do I overwrite JUST that one function in my own module? I've done similar things with classes before, but not sure what to do with a Trait.

# Answer
For those that may run across this. I could not find a way to override (don't think it's possible). I did eventually settle on modifying the line of the file myself, running a git diff, and adding my own patch to my composer.json. In this way, I can run updates and use composer correctly. It's not a patch others will need, but how it was solved might help others.
> 0 votes

---
Tags: 8
---


# Question
Title: Live content migration to a separate server for important content
My clients are now spending a lot of money daily on creating content on my system. A few hours lost could be quite bad for them, and in turn, for me.

Most of the site is fine on daily backups, but there is a content type I'd like to offload onto a backup server when nodes are created or updated. Is this possible? Preferably sending a node object and related files.

I haven't had an issue in a long time but I'm feeling the stress with their investment.

# Answer
With the Backup & Migrate module installed, navigate to: /admin/config/development/backup\_migrate/schedule to create & manage database or site file backup schedules:

Note that there are options to backup the default Drupal database, all of the site files, or just specific Drupal file directories.

Under "Settings" there is an admin section to configure the destination address for the backups at: /admin/config/development/backup\_migrate/settings/destination

Schedule a complete site backup daily with hourly database backups. The Backup & Migrate developers have the NodeSquirrel service available for offsite backups - but this service only works with the D7 module & is unavailable for D8 currently.

With cPanel, there are better options available now with cPanel Backups & Backup Tool:

If you need a more professional backup service, look into Veeam.
> -1 votes

---
Tags: migrations, backups
---


# Question
Title: Post Migrate Development to Live Production Server issue
I'm positive I've successfully done this before but in testing a project today from a localhost Development (Ubuntu) to a live Production server (shared server, general market provider ala Bluehost) I'm getting a White Screen Of Death (WSOD) after importing the main filename.sql via phpmyadmin and I'm not sure why it's not working correctly.

Here's what I'm doing:

1) - copy via FTP all core and /sites/modules and my relevant dir contents.

2) - setup all Database with user/passwords including Admin as required.

3) - install generic Drupal v.7.2x onto server using Drupal /install.php

this then gets me a 'Standard' install but without all my data content but all accessable as per a generic standard Drupal install.

Next, back to phpmyAdmin, go to the relevant DB structure and chose 'Import', because I'm using a raw. sql I can just select the 22.5mg .sql file and wait, then I get the 'Success' message. I cannot use Backup & Migrate (M&G) Module due to the file limitation given my B&M.

After this point, I get a WSOD and cannot move forward.

The only thing I know that is different is that on my locahost Dev I used a simple 'Admin' name and a simple Password such as 'DeadEasy'. On the live Prod site, I changed this for better security to a name relevant to a SuperUser (ie AlphaBravoCharlie,(thats NOT real!!), Password: such as "DrG6%7DyE".

FYI, I did have some Fatal Errors yesterday based on Memory allocation, (I'm allowed 128meg max) but the example highlighted here is after I reduced module load to resolve the previous fatal error given.

Can anyone shed any light on what may have happened?

Notes #1: I had thought it may have been that I hadn't uploaded my 'sites/all/themes', but it wasn't. #2 any other 'test' index.html work fine (as in temporarily disabling the index.php).

# Answer
The first place to look would be the BlueHost Error Logs; their article at https://my.bluehost.com/cgi/help/562 describes where to find them within cPanel. If you're seeing failed calls to functions / modules that you know exist in your codebase, there's a strong probability that Drupal's registry is not current.

Drupal caches extension (module and theme) file locations within its registry. Given that you are importing an existing site database into a different structure, those locations are out-of-date.

If you haven't already, I suggest installing Drush (the DRUpal SHell) using the instructions at https://drupal.org/node/1181480

Then, clear the site cache:

```
drush cc all

```

If that does not clear it up, the next step is a registry rebuild. This forces Drupal to rescan executable file locations. Install the Drupal contrib project registry\_rebuild using the instructions on the project homepage, then

```
drush rr

```

to rebuild the registry.
> 3 votes

# Answer
If the MySQL file is too large for the web host's limits in phpMyAdmin, consider using the BigDump script. Download & edit the BigDump PHP file, then run from a web browser at: http://www.yourdomain.com/dump/bigdump.php to upload the MySQL in a staggered manner to get around the time-out limits. This requires the "Drop Table" setting on the original MySQL export file to be used in the database transfer. Also, make sure to use PHP 7.2+ settings for Drupal 8 at Bluehost.
> 1 votes

---
Tags: migrations, installation
---


title: Running Drush on the server

text:
# Question
Title: Running Drush on the server
I want to run Drush on a OpenSuse Linux Version 12.1 box 32-bit machine. I have a server admininistrator that takes care for the backend.

What do I need to ask him, in order to get Drush running on the server?

# Answer
> 2 votes
If you want to run Drush, you need ssh access to the server. This means you need your sysadmin to make an account in the server for you.

To run the "pm-" commands, and a few others, this account will need to have both read and write access to the DOCROOT. There are a few ways to do this; let your admin pick which is best for your setup.

You will also need PHP available from the command line, preferably with the same php.ini that the website uses.

# Answer
> -2 votes
Drush should be installed as a dependency of Composer, so first install Composer:

```
sudo zypper install php-composer

```

Then use Composer to install the Drush 8.x version:

```
composer global require drush/drush:8.*

```

To register Drush as a dependency of Composer:

```
composer require drush/drush

```

To integrate Drush with the Bash CLI utility:

```
drush init

```

OpenSuse Direct Install Packages:

---
Tags: drush
---


# Question
Title: How to add autocomplete to a textarea on a form?
I would like to add autocomplete functionality to a textarea form element in Drupal 8. I have a fully functioning custom textfield which has autocomplete working on the same form. I have confirmed that the Textarea element has the processAutocomplete function. Therefore, I'm uncertain as to why this wouldn't work.

I realize that adding an autocomplete to a textarea is a bit unorthodox, but that's not the question. I have seen this done in Drupal 8, so I know it's possible. I just don't know if they used a form to accomplish it.

I have added the appropriate code in the routing file:

```
my_module.autocomplete_name:
   path: '/my-module/test-autocomplete'
   defaults:
     _controller:'\Drupal\my_module\Controller\Controller::functionName'
     _format: json
   requirements:
     _access: 'TRUE'

```

I have also added the field appropriately on the form:

```
$form['text_area'] = [
  '#type' => 'textarea',
  '#title' => t('Test text area'),
  '#default_value' => $authentication_configuration->get('text_area'),
  '#autocomplete_route_name' => 'my_module.autocomplete_name',
];

```

Finally, I have the appropriate logic in the referenced controller:

```
public function testAutocomplete(request $request) {
  // Set up the array to be returned and get the user input
  $matches = array();
  $input = $request->query->get('q');
  // If there is user input
  if ($input) {
    $words = explode(' ', $input);
    foreach ($words as $word) {
      /*
       * Logic for generating an autocomplete list of matches
       */
    }
  }
  return new JsonResponse($matches);
}

```

# Answer
> 6 votes
Textareas don't support autocomplete out of the box, you have to get a bit creative to make it work.

First you'll need to override the `attach` method of the autocomplete behaviour; add a library with a JS file containing this:

```
(function ($) {
  Drupal.behaviors.autocomplete.attach = function attach(context) {
    var $autocomplete = $(context).find('input.form-autocomplete,textarea.form-autocomplete').once('autocomplete');
    if ($autocomplete.length) {
      var blacklist = $autocomplete.attr('data-autocomplete-first-character-blacklist');
      $.extend(Drupal.autocomplete.options, {
        firstCharacterBlacklist: blacklist || ''
      });

      $autocomplete.autocomplete(Drupal.autocomplete.options).each(function () {
        $(this).data('ui-autocomplete')._renderItem = Drupal.autocomplete.options.renderItem;
      });

      $autocomplete.on('compositionstart.autocomplete', function () {
        Drupal.autocomplete.options.isComposing = true;
      });
      $autocomplete.on('compositionend.autocomplete', function () {
        Drupal.autocomplete.options.isComposing = false;
      });
    }
  };
})(jQuery);

```

The function is a straight copy of the core `attach` method, with the `textarea` added to the selectors, and a few references altered.

Then you'll need to add the autocomplete process method to the form element (it's not there by default), and also reference your library:

```
use Drupal\Core\Render\Element\Textarea;

...

$form['text_area'] = [
  '#type' => 'textarea',
  '#title' => t('Test text area'),
  '#default_value' => $authentication_configuration->get('text_area'),
  '#autocomplete_route_name' => 'my_module.autocomplete_name',
  '#process' => [
    [Textarea::class, 'processAutocomplete'],
  ],
  '#attached' => [
    'library' => [
      'mymodule/autocomplete'
    ],
  ],
];

```

Clear the cache and you should see the results immediately.

---
Tags: 8, forms, ajax
---


# Question
Title: Add class to form-tag in in views exposed form via template_preprocess_views_exposed_form()
I wonder how I can add a class to the form tag (of the \_search\_content\_ view's) exposed form using `template_preprocess_views_exposed_form()` (or even better `template_preprocess_views_exposed_form__search_content()`). The form is **exposed as block**.

It works so far using the following code in MY\_THEME.theme:

```
function MY_THEME_preprocess_form(&$variables) {
  if ($variables['element']['#form_id'] == 'views_exposed_form' && $variables['element']['#view_id'] == 'search_content') {
    $variables['attributes']['class'][] = 'row';
  }
}

```

But I can't get it work for:

```
function MY_THEME_preprocess_views_exposed_form__search_content(&$variables) {
  // I tried:
  // $variables['attributes']['class'][] = 'does-not-work';

  // and also:
  // $variables['attributes'] = new Attribute($variables['attributes']);
  // $variables['attributes']->addClass('does-not-work');

  // and also manipulating $variables['form'], but this didn't work either.
}

```

The function gets called, but it's not adding the class to the form tag. **Is it possible to get it work with `template_preprocess_views_exposed_form()`?**

Screenshot of `kint($variables)` output, for each function:

Is it possible to get it work with template\_preprocess\_views\_exposed\_form()?

# Answer
**Short version:** You cannot use `hook_preprocess_views_exposed_form()` to modify the `<form>` tag. The `<form>` tag is independently rendered by a `form` theme hook set through the `#theme_wrappers` render property. Which means that it is completely disassociated from the actual `views_exposed_form` theme hook that renders the (internal) content of the views exposed form.

**Long version:**

1. `views_theme()` defines the `views_exposed_form` theme hook as returning a form render element. That means that it will eventually be using (unless somehow overridden) the definition provided by the Form render element. If you take a look at that, you will see that that definition sets the`'#theme_wrappers'` property to `['form']`. No surprises so far. :-)
2. At some point during the theming of a views page (or block), the theme hook `views_view` comes into play; this theme hook contains the `exposed` render variable that is essentially the exposed form you are interested in. The render array of `exposed` holds the following two relevant properties:

* `#theme => views_exposed_form` (and other more specific ones like `views_exposed_form__search_content`): This is the theme hook that will provide the content of the form, i.e. everything inside the `<form>` tag. This is what you get access to and given the option to modify when calling `template_preprocess_views_exposed_form()`. This theme hook **is not** responsible for rendering the `<form>` tag and does not allow you to change it.
    * `#theme_wrappers => ['form']` : This is the `#theme_wrappers` list of theme hooks; `form` will provide the actual `<form>` tag. It will essentially enclose the markup generated by the `#theme` theme hook into a `<form>` tag. This is what you get access to when calling `template_preprocess_form()`. This theme is responsible for rendering the `<form>` tag and does allow you to change it.

**Implementation suggestion**

If you would rather let `hook_form_alter()` implementations handle form-related logic, you could still use another preprocess hook to implement the functionality you want, namely the `preprocess_views_view()` hook.

```
function MY_THEME_preprocess_views_view(&$variables) {
  if ($variables['id'] === 'search_content') {
    $variables['exposed']['#attributes']['class'][] = 'row';
  }
}

```

Good luck, this is a really good question!
> 9 votes

# Answer
It is because the hook fired for the exposed form is actually 'form' as you can see in the below screenshot:

And this is why it is always a good idea to enable twig debug
Hope that helps!

EDIT
To elaborate more:
The views exposed form preprocess function does not edit the form itself because this is a different hook. The proper way to add a class to the form is to alter the form it self or use https://api.drupal.org/api/drupal/core%21includes%21form.inc/function/template\_preprocess\_form/8.6.x because this function is the responsible for adding attributes to the form itself. Also check the twig source of https://api.drupal.org/api/drupal/core%21themes%21stable%21templates%21form%21form.html.twig/8.6.x You'll see that the attributes are printed there.
> 0 votes

# Answer
If you want just add Class to `form` tag to the exposed filter you can do it with hook\_form\_FORM\_ID\_alter no need to `template_preprocess_views_exposed_form()` or `template_preprocess_views_exposed_form__search_content()` Like the following:

```
use Drupal\Component\Utility\Html;
/**
 * Implements hook_form_FORM_ID_alter().
 */
function myModuleName_form_views_exposed_form_alter(&$form, \Drupal\Core\Form\FormStateInterface $form_state, $form_id) {
  $form['#attributes']['class'][] = Html::cleanCssIdentifier('your-new-class');
}

```
> 0 votes

---
Tags: views, 8, theming
---


# Question
Title: Get Last Node Access Timestamp Per Node by User
Drupal 8.x

I am trying to compare the current users last access timestamp to other timestamp values and am wondering how to get the current users "last\_access" in HOOK\_preprocess\_node().

**MYMODULE.module:**

```
function MYMODULE_preprocess_node(&$variables) {
  kpr(\Drupal::currentUser()->getLastAccessedTime());
}

```

This returns a timestamp, however, it updates quite infrequently and not on node load, or even after cache clear, and does not seem node specific.

I've accessed this value in HOOK\_preprocess\_user() with `$variables['elements']['#user']->access->value`, however, I would like the same value available in node.

How do you get the timestamp of the user's last view of a node per node?

# Answer
This doesn't work because the node is cached. Disabling caching of the node wouldn't be a good idea, performance-wise. Try instead a lazy builder to retrieve the access time and create a placeholder, so that the node can be cached, no matter how often the access time is updated:

```
/**
 * Implements hook_preprocess_HOOK() for node templates.
 */
function mymodule_preprocess_node(&$variables) {
  $variables['content']['timestamp'] = [
    '#lazy_builder' => ['mymodule_timestamp_current_user', []],
    '#create_placeholder' => TRUE,
  ];
}

/**
 * #lazy_builder callback; builds timestamp markup with current user access time.
 */
function mymodule_timestamp_current_user() {
  $timestamp = \Drupal::currentUser()->getLastAccessedTime();
  return [
    '#markup' => $timestamp,
    '#cache' => ['max-age' => 0],
  ];
}

```

**Drupal ^9**: The callback needs to be in a class implementing TrustedCallbackInterface or RenderCallbackInterface. See https://drupal.stackexchange.com/a/303039/47547 for a code example.

---

**Statistics**

This code extends the code you had so far. If you are referring to statistical node view data have a look at the core Statistics module, which provides a timestamp when the node was last viewed. However, not on a per user basis, but this gives you an idea how you can record such statistical data.
> 3 votes

# Answer
`User::getLastAccessedTime` will only give you

> The timestamp when the account last accessed the site.

Under no circumstances this value is going to be associated to a node.

---

So what I'd do now is the following:

1. Add a new field to the user that's going to be used to store the data we need. Maybe a JSON field (module A or module B, I don't know which one is better) or any other custom field that is capable of holding an array of nodes and timestamps. Hide that field from display.
2. Add a new pseudo/extra field to your node type that will get and display the last accessed time for the current node, and then will set a new timestamp. Which maybe could look like this (untested!):

---

```
use Drupal\node\Entity\NodeType;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\Display\EntityViewDisplayInterface;

function MYMODULE_entity_extra_field_info() {

  $extra = [];

  foreach (NodeType::loadMultiple() as $bundle) {

    $extra['node'][$bundle->id()]['display']['last_accessed'] = [
      'label'       => t('Node Last Accessed'),
      'description' => t('Displays the timestamp the current node got last accessed by the current user'),
      'visible'     => FALSE, // Initially set to hidden on Display Settings.
    ];
  }

  return $extra;
}

function MYMODULE_node_view(array &$build, EntityInterface $entity, EntityViewDisplayInterface $display, $view_mode) {

  $current_uid = \Drupal::currentUser()->id();
  $current_user = \Drupal\user\Entity\User::load($current_uid);

  $current_nid = $entity->id();

  // Now you have the current user entity and the current node ID.

  // Now you need to get the value of the custom field which probably should
  // be an associated array of node IDs and timestamps.
  $access_data = $current_user->get('field_node_access_data')->getValue();

  // Now you need to look up if the current node ID already is in it and if so,
  // retrieve its timestamp value.
  $timestamp = ... // your job.

  if ($display->getComponent('last_accessed')) {

    $build['last_accessed'] = [
      '#markup' => $timestamp,
      // I hope this works that way. That the cache gets disabled properly for
      // just this pseudo field.
      '#cache' => ['max-age' => 0],
    ];
  }

  // And now finally after the timestamp has been rendered it needs to get updated.
  $current_user->set('field_node_access_data')->... // Your job. Probably something with time().
  $current_user->save();
}

```

---

Again, this is absolutely untested. And if the cache busting for the markup in the pseudo field display doesn't work I'd probably try the same approach with a custom block. Or the lazy builder approach as highlighted in the other answer. But a block can easily be excluded from cache and you can use a theme, contexts and what else a little bit easier maybe than what you have in a preprocess hook.
> 1 votes

# Answer
Working with database tables I have come up with this option so far:

**MYMODULE.module:**

```
/**
 * This creates a database to store the last access timestamp per node per user.
 */

/**
 * implements HOOK_preprocess_node().
 */
function node_access_timestamp_by_user_preprocess_node(&$variables) {

  // Set current user ID.
  $uid = \Drupal::currentUser()->id();

  // Check if user is authenticated.
  if ($uid != 0) {

    // Get current NID, Timestamp, and DB.
    $currentNID = \Drupal::routeMatch()->getRawParameter('node');
    $currentTimestamp = intval(time());
    $database = \Drupal::database();

    // Query our DB.
    $queryDBEntries = $database
      ->select('node_access_timestamp_by_user', 'timestamp')
      ->fields('timestamp')
      ->condition('uid', $uid, "=")
      ->condition('nid', $currentNID, "=");

    // Fetch DB data.
    $result = $queryDBEntries->execute()->fetchAll();

    // Check if table row exists and overwrite.
    if ($result[0]->uid == $uid && $result[0]->nid == $currentNID) {
      $database
        ->update('node_access_timestamp_by_user')
        ->fields([
          'timestamp' => $currentTimestamp,
        ])
        ->condition('uid', $uid, '=')
        ->condition('nid', $currentNID, '=')
        ->execute();
    }

    // Check if table row does not exist and create.
    if ($result[0]->uid != $uid && $result[0]->nid != $currentNID) {
      $database
        ->insert('node_access_timestamp_by_user')
        ->fields([
          'uid' => $uid,
          'nid' => $currentNID,
          'timestamp' => $currentTimestamp,
        ])
        ->execute();
    }

    // Set our $variables to print in twig.
    if ($result[0]->nid == $currentNID) {
      $variables['nodeAccessTimestampByUser_uid'] = $result[0]->uid;
      $variables['nodeAccessTimestampByUser_nid'] = $result[0]->nid;
      $variables['nodeAccessTimestampByUser_timestamp'] = $result[0]->timestamp;
    }

  }

}

/**
* Implements HOOK_install().
*
* Creates some default entries on this module custom table.
*
* @see hook_install()
*
* @ingroup node_access_timestamp_by_user
*/
function node_access_timestamp_by_user_install() {

  $currentTimestamp = intval(time());
  $database = \Drupal::database();

  // Add a default entry.
  $fields = [
    'uid' => '1',
    'nid' => '1',
    'timestamp' => $currentTimestamp,
  ];
  $database
    ->insert('node_access_timestamp_by_user')
    ->fields($fields)
    ->execute();
}

/**
* Implements HOOK_schema().
*
* Defines the database tables used by this module.
*
* @see hook_schema()
*
* @ingroup node_access_timestamp_by_user
*/
function node_access_timestamp_by_user_schema() {

  $currentTimestamp = intval(time());

  $schema['node_access_timestamp_by_user'] = [
    'description' => 'Stores node access timestamp by user per node.',
    'fields' => [
      'uid' => [
        'type' => 'int',
        'not null' => TRUE,
        'default' => 1,
        'description' => "User's {users}.uid",
    ],
      'nid' => [
        'type' => 'int',
        'not null' => TRUE,
        'default' => 1,
        'description' => "Node ID",
    ],
      'timestamp' => [
        'type' => 'int',
        'not null' => TRUE,
        'default' => $currentTimestamp,
        'description' => "Timestamp",
    ],
    ],
    'primary key' => ['nid'],
    'indexes' => [
      'uid' => ['uid'],
      'nid' => ['nid'],
      'timestamp' => ['timestamp'],
    ],
  ];

  return $schema;
}

```

**node.html.twig:**

```
<table>
  <tbody>
      <tr>
          <td>UID</td>
          <td>{{ nodeAccessTimestampByUser_uid }}</td>
      </tr>
      <tr>
        <td>NID</td>
        <td>{{ nodeAccessTimestampByUser_nid }}</td>
    </tr>
    <tr>
        <td>Timestamp</td>
        <td>{{ nodeAccessTimestampByUser_timestamp }}</td>
    </tr>
  </tbody>
</table>

```

This is only moderately tested, and I would not be someone who could talk about performance impacts.

I am overwriting tables fields instead of adding to... I feel that if you stored every instance like a `history`, the database would get gigantic.
> 0 votes

---
Tags: 8, nodes, hooks
---