0

My use is case is that I wish to show an advert every 3rd comment.

The forum module added the comment field to the forum content type. So the field name is comment_forum.

I have a block that contains the embed code for the ads. Using twig_tweak module to render that block in my template using {{ drupal_entity('block_content', '5') }}

I then created a template file called field--comment-forum.html.twig.

So far I have this:

{%
  set classes = [
    'field',
    'field--name-' ~ field_name|clean_class,
    'field--type-' ~ field_type|clean_class,
    'field--label-' ~ label_display,
    'comment-wrapper',
  ]
%}
{%
  set title_classes = [
    'title',
    'comment-title-main',
    label_display == 'visually_hidden' ? 'visually-hidden',
  ]
%}

{% if comments and not label_hidden %}
    
  <div class="my-5 col-print-12" id="comment-container"> 
    <section{{ attributes.addclass(classes) }}>
      {{ title_prefix }}
      <h2{{ title_attributes.addClass(title_classes) }} {{ attributes.setAttribute('id', 'comments') }}>{{ label }}</h2>
      {{ title_suffix }}

          {% set pre_render = comments['#pre_render'] %}
            {% for key, item in comments if key|first != '#'%}
                {% if key != 'pager' %}
                    {% set item = { '#pre_render': pre_render, 0: item } %}
                    {% if loop.index0 is divisible by(3) %}
                        {{ item }}
                        <div class="ads">{{ drupal_entity('block_content', '5') }}</div>
                    {% else %}
                        {{ item }}
                    {% endif %}
                {% else %}
                  <div class="comment-pager">{{ item }}</div>
                {% endif %}
          {% endfor %}
    </section>
  </div>
{% endif %}

{% if comment_form %}
  <div class="container">
  <div class="row justify-content-md-center">
    <div class="col col-md-8 p-2 p-md-4 my-5 bg-light rounded comment-new-container d-print-none">
      <h2 class="title comment-form__title comment-new-title">{{ 'Add new comment'|t }}</h2>
      {{ comment_form }}
    </div>
  </div>
</div>
{% endif %}

This works, as in the block with the ads are shown every 3rd comment. However it messes up the markup.

Without the template the markup for a comment with replies is this:

Without template

This is the markup for the same comments with the above template:

with template

I also notice the comments_ajax_pager_wrap container div is also not included either. That's added by the comments_ajax_pager module.

This is my first Drupal 9 website. So I'm new to twig, any ideas? I feel like I may even be in the wrong template?

3
  • Probably due to you using the pre_render array item in {% set item = { '#pre_render': pre_render, 0: item } %} Why are you using that? Feb 23, 2023 at 16:30
  • @unusedspoon I got that from another post on here. If I'm honest, I don't understand it because I'm new to twig.
    – gMaximus
    Feb 23, 2023 at 17:20
  • @unusedspoon This is my reference: drupal.stackexchange.com/questions/263158/…
    – gMaximus
    Feb 23, 2023 at 18:39

3 Answers 3

1

Seems like you've already got it working from our comments in your other answer (which you can probably just mark as accepted if you're using the approach), but just for your own reference, here's how you might be able to fix your field template approach.

Per your comment mentioning that your initial implementation in the field template was breaking comment_reply_count module, I think it's because of your structure. If you look at https://git.drupalcode.org/project/comment_reply_count/-/blob/8.x-1.x/js/comment_reply_count.js, it relies on $(this).parents('article.comment').next().slideToggle();. This line tries to toggle the element directly after article.comment, which is normally <div class="indented">. Since you're rendering your ad div after the comment, I'd wager that it's trying to toggle that instead.

To maintain the functionality, I think you'd need to:

  1. Determine whether a given comment is a parent comment or a child/indented comment. Comments keep track of this on the pid value, which stands for Parent ID. If the PID is empty, it's a parent comment. Comments have a hasParent() method that checks this.
  2. If it is a parent, then render the ad before the comment if it is divisible by 3 (or 4, or whatever).
            {% set parent_comment_counter = 0 %}
            {% for key, item in comments if key|first != '#'%}
                {% if key != 'pager' %}

                    {% if not item['#comment'].hasParent() %}
                      {% set parent_comment_counter = top_level_comment_counter + 1 %}
                      {% if top_level_comment_counter is divisible by(3) %}
                        <div class="ads">{{ drupal_entity('block_content', '5') }}</div>
                      {% endif %}
                    {% endif %}

                    {% set item = { '#pre_render': pre_render, 0: item } %}
                    {{ item }}
                {% else %}
                  <div class="comment-pager">{{ item }}</div>
                {% endif %}
          {% endfor %}

If you wanted to do this in preprocess instead, you could do something like

hook_preprocess_field__comment_forum(&$variables) {
  $parent_comment_counter = 0;

  foreach($variables['comments'] as $k => $comment_item) {
    if(!is_array($comment_item) || !isset($comment['#comment'])) {
      continue;
    } 

    $variables[$k]['show_ad'] = false;

    /** @var Drupal\comment\Entity\CommentInterface $comment_entity */
    $comment_entity = $comment['#comment'];
    if($comment_entity->hasParent()) {
      continue;
    }

    $parent_comment_counter += 1;
    $variables[$k]['show_ad'] = $parent_comment_counter % 3 === 0;
  }
}

in which case your template could then look like

            {% set parent_comment_counter = 0 %}
            {% for key, item in comments if key|first != '#'%}
                {% if key != 'pager' %}

                    {% if item.show_ad %}
                        <div class="ads">{{ drupal_entity('block_content', '5') }}</div>
                    {% endif %}

                    {% set item = { '#pre_render': pre_render, 0: item } %}
                    {{ item }}
                {% else %}
                  <div class="comment-pager">{{ item }}</div>
                {% endif %}
          {% endfor %}

That's the gist of it. You'd need to modify it if you wanted to include the ad around indented comments to ensure that you aren't including the ad block above the first indented comment after a parent, as that would break it in the same way as before.

I'm not sure exactly why <div class="comments_ajax_pager_wrap"> was disappearing, though. That wrapper is added by comment_ajax_pager_entity_display_build_alter(), so maybe you could debug from there.

1
  • Thanks for all your help. I certainly learned some new code. All the best
    – gMaximus
    Feb 28, 2023 at 11:48
0

This is EASY-PEASY!

I already build this in some of my previous projects.

In your template file field--comment-forum.html.twig, you can iterate over the comments using a loop and a counter variable that keeps track of the comment number. You can use the loop.index variable provided by Twig to get the current iteration index.

{% set counter = 0 %}
{% for comment in comments %}
  {% set counter = counter + 1 %}
  {# Display the comment content #}
  {{ comment.content }}
  {# Display the ad after every 3rd comment #}
  {% if counter % 3 == 0 %}
    {{ drupal_entity('block_content', '5') }}
  {% endif %}
{% endfor %}

I hope this help!

1
  • Thanks for your time offering a solution. However the comments don't get rendered. Only the ads. My comments are threaded. So I'm not so sure this approach with a field template will work. If the comments did appear, I'm sure it would result in the markup in my original question. That's been my issue.
    – gMaximus
    Feb 25, 2023 at 13:18
0

A week later and I changed tact completely. I questioned if it would be possible to work with comment.html.twig instead of the field template.

So in my MY_THEME.theme file I pass the comments weight to the template, like this:

Updated thanks to @mrweiner's help in the comments

/**
 * Implements hook_preprocess_comment().
 */
function MY_THEME_preprocess_comment(&$variables) {
    /** @var \Drupal\comment\CommentInterface $comment */
    $comment = $variables['elements']['#comment'];
    $node = $comment->getCommentedEntity();
    $cid = $comment->id();
    $entity_manager = \Drupal::entityTypeManager();
    try {
        
        // Load comments for the node
        /** @var \Drupal\comment\CommentStorageInterface $storage */
        $storage = $entity_manager->getStorage('comment');
        if ($node->hasField('comment_forum')) {
            /** @var \Drupal\comment\CommentFieldItemList $comment_field */
            $comment_field = $node->get('comment_forum');
        } elseif ($node->hasField('field_comments')) {
            /** @var \Drupal\comment\CommentFieldItemList $comment_field */
            $comment_field = $node->get('field_comments');
        }
        $comments = $storage->loadThread($node, $comment_field->getFieldDefinition()->getName(), \Drupal\comment\CommentManagerInterface::COMMENT_MODE_FLAT);

        // Ensure there are comments
        if (empty($comments)){
          return;
        }

       $comment_index = array_key_first(array_filter(array_values($comments), fn ($this_comment) => $this_comment->id() === $comment->id())); 
       $weight = $comment_index + 1;
       $variables['displayAds'] = $weight % 3 === 0;

      } catch (\Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException $e) {
      } catch (\Drupal\Component\Plugin\Exception\PluginNotFoundException $e) {
      }
}

After implementing that hook I simply added this to comment.html.twig where I wanted my block to appear:

{% if displayAds %}
       <div class="comment-ads">{{ drupal_entity('block_content', '5') }}</div>
{% endif %}
7
  • 1
    A slightly cleaner way to do this would be to set a $variables['display_ads'] or similar in your preprocess hook, instead of doing everything with $weights like you're doing now. So like $variables['display_ads'] = $comment_weight % 3 === 0. It's also kind of heavyhanded to do this loop for every comment. Could do something like $comment_index = array_key_first(array_filter($comments, fn ($this_comment) => $this_comment->id() === $comment->id())); $weight = $comment_index + 1; which avoids all of the looping. Adding the +1 since the comments array starts with key of 0.
    – Mrweiner
    Feb 23, 2023 at 22:11
  • Thank you @mrweiner. I'm going your route. Could have done with you a week ago lol. I have noticed that the ads appear in random places. Sometimes after 2, 3 or 4 comments? This is also the case when there are just a series of comments, not threaded.
    – gMaximus
    Feb 25, 2023 at 16:04
  • @Mrweiner Is this relying on the comment ID being divisible by 3? I've been hoping to use the comments weight on the entity that they're attached to. Making them show every 3rd comment.
    – gMaximus
    Feb 25, 2023 at 20:56
  • I don’t think it should depend on the comment ID being divisible by 3. Does loadThread() return an array where the keys are incremental (0,1,2,…) or are the keys the comment ids? Sounds like it might be the latter. If the latter, you can use array_values($comments) to reset all of those keys to be incremental instead. I forgot that entity queries do use entity IDs as the keys.
    – Mrweiner
    Feb 26, 2023 at 18:38
  • Also FWIW, I’d generally try to address this in the comment field template instead of the comment template itself, since the ads are really related to the container and not the comment itself, but given that you were trying that already and it didn’t work, this is the next best bet. Rendering comments in particular can be a PITA. Sometimes you just gotta get it workin!
    – Mrweiner
    Feb 26, 2023 at 18:44

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.