0

I want to have more flexibility in creating content as usual. Therefore, I use the Paragraphs module, and I also disabled the standard body text. So now, a user can now freely decide how he wants to mix images, text and other content (like embedded videos). For example, an article could now start with a text followed by a full size image, or with 2 half size images followed by a full size video and then the text and so on.

So far so good. Now the problem is that I loose the standard summary function since the default body text is just disabled. This is especially a problem for any list of articles, i.e. the front page.

I have managed to kinda solve this problem by a custom preprocess hook:

function MY_THEME_preprocess_node(array &$variables) {

  $node  = \Drupal\node\Entity\Node::load($variables['node']->id());

  if ($node->field_paragraph && !empty($node->field_paragraph->getValue())) {

    foreach ($node->field_paragraph->getValue() as $key => $value) {
      $paragraph = FALSE;
      if (!empty($value['entity'])) {
        // Use that, we're probably previewing.
        $paragraph = $value['entity'];
      } elseif (!empty($value['target_id'])) {
        $paragraph = Paragraph::load($value['target_id']);
      }

      if (!empty($paragraph) && $paragraph instanceof Paragraph) {

        $summary = $paragraph->getSummary();

        if ($summary && $paragraph->type->getValue()[0]["target_id"] === "text") {
          $variables['summary'] = $summary;
          break;
        }
      }
    }
  }
}

What this does is to loop over all paragraph entries (if any) in a given node and stop iterating at the first occurence of a paragraph type named "text", if there is also a result for the getSummary() function, i.e. this paragraph item also contains some text. Keep in mind that I am far from being a Drupal expert, so this code might not be the optimal way?

However, I cannot seem to control the length of the summary, also any headlines defined in the same paragraph are included as well. Last but not least, the result also contains unneeded markup, so in my node--article--teaser.html.twig twig template I have to strip out those tags again:

<p>{{ summary|striptags }}</p>

The next problem arises when I want to use that summary for the description meta tags on any given article page (so in this case, NOT the pages that contain only lists of articles). Usually I would just use the Metatags module and use this for the description: [node:summary]

Of course that does not work here, and I can imagine that further down the road I will encounter more problems like this.

Therefore, it seems to me that the cleanest way would be to kinda emulate [node:summary], i.e. that this value is automatically calculated by something similar to the function I used above. So for the consumer of this value this would look identical as if the page / node had an actual "default" summary.

Now, is this possible at all, and if "yes" how can I achieve this? Any insights are greatly appreciated!

6
  • Welcome to Drupal Answers! :-) Maybe try to keep it simple and just add another textfield to be used as "Summary" on top of the Paragraphs field and use its field formatters or other features to limit characters on input or output.
    – leymannx
    Commented Jan 10, 2020 at 21:15
  • Yes, the easy thing to do here would be to add a text field to let the user add a summary (make it required) and output it in twig before the paragraph field. Then you can lose all this logic. That basically puts it back to the way it was, you are overthinking this.
    – Kevin
    Commented Jan 11, 2020 at 0:52
  • Also, if the user does not create a text paragraph or they add dozens of paragraphs to the node, two things will happen. In the first scenario there would still be no summary text. In the latter, you will possibly see performance issues especially if you are listing multiple nodes in a page. It is just more practical to reduce this to a field specifically for this purpose.
    – Kevin
    Commented Jan 11, 2020 at 1:06
  • Yeah of course I could make another field ... but I know my users ;) I.e. they would forget, or complain why they have to edit two identical pieces of text all the time. I still would like to have an "automatic" solution! Anyone? Commented Jan 11, 2020 at 13:45
  • Mark the field required? The summary doesn't have to be identical. The point of the field is explicitly for the purpose of setting a summary - you don't have to print it in the full node template. This in essence is how teaser/full view display works out of the box.
    – Kevin
    Commented Jan 11, 2020 at 18:23

1 Answer 1

0

Well, looks like it is time again to answer my own question!

So, since the Metatag Module I want to also use is based on tokens anyway, I have managed to implement this using my own module which in turn just defines an additional custom token which in turn returns my desired summary. Here is what my current custom_summary_token.module code looks like right now (still work in progress):

use Drupal\Core\Render\BubbleableMetadata;
use Drupal\paragraphs\Entity\Paragraph;

function custom_summary_token_token_info(){
    $type = [
        'name' => t('Nodes'),
        'description' => t('Tokens related to individual nodes.'),
        'needs-data' => 'node'
    ];

    $node['custom_summary'] = array(
        'name' => t("Custom Summary"),
        'description' => t("Token for dynamically generating a node summary, depending on the content. Used for extracting text when not using standard body field, but only paragraphs."),
    );

    return [
        'types' => ['node' => $type],
        'tokens' => ['node' => $node],
    ];
}

function custom_summary_token_tokens($type, $tokens, array $data, array $options, BubbleableMetadata $bubbleable_metadata){

    if (!isset($data['node'])) { return; }

    $replacements = array();
    $node = $data['node'];

    if($type == 'node' && !empty($node)) {

        foreach ($tokens as $name => $original) {

            switch ($name) {

                case 'custom_summary':

                    if (!empty($node->body->value)) {

                        $text = strip_tags($node->body->value);

                    } else if ($node->field_summary && !empty($node->field_summary->getValue())) {

                        $text = $node->field_summary->getValue()[0]["value"];

                    } else if ($node->field_paragraph_test && !empty($node->field_paragraph_test->getValue())) {

                        foreach ($node->field_paragraph_test->getValue() as $key => $value) {
                            $paragraph = FALSE;
                            if (!empty($value['entity'])) {
                                $paragraph = $value['entity']; // Use that, we're probably previewing.
                            } elseif (!empty($value['target_id'])) {
                                $paragraph = Paragraph::load($value['target_id']);
                            }
                            if ($paragraph !== FALSE && $paragraph instanceof Paragraph && $paragraph->getType() === "text") {
                                $text = strip_tags($paragraph->get('field_text_body')->value);
                                break;
                            }
                        }

                    }

                    if (strlen($text) > 150) {
                        $text = substr($text, 0, 150)." ...";
                    }

                    $text = str_replace("&nbsp;", " ", $text); // filter out artefact when editing with chrome
                    $replacements[$original] = $text;
                    break;
            }
        }
    }

    return $replacements;

}

So what this does in that tokens hook is (after checking that we have the right token of course) first to look for an ordinary default body text which is not empty. Simply because then this also works for my other (in this case also simpler) content types. Then, the code looks for a custom field named field_summary. Turned out that some users actually want to create a custom summary, so yeah now they can do that as well. For those who don't (the majority ...), the code then looks for the first paragraph instance which is also a text block (and not empty). Last but not least, tags are stripped, and the whole thing is truncated to 150 chars, with some "..." appended for good measure.

Now, I can use this just like any token in my Metatag setup: [node:custom_summary], and - with a little help from the twig tweak module - also in any template: 

{{ drupal_token('node:custom_summary', { node: node }) }}

Of course this implementation still might be too naive or there would be an even better way, also I am not sure how big the performance impact of such a solution is. But hey - at least this works!

What would you think?

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.