REST Views: Render Placeholders not Being Replaced with Actual Values

Created on 1 February 2019, over 6 years ago
Updated 18 August 2025, 7 days ago

Problem/Motivation

I am working site that is using a REST export Views display and found that Flag (module) links that are rendered in the View do not work because they have bad CSRF tokens. When rendering the same content as a Block (or any other non-REST export display), the links work correctly. When comparing the two links, I noticed that the CSRF tokens were different depending on which display I was using.

What I expect to happen is that the View will render a working CSRF link regardless of which Display plugin is used.

In order to reproduce this without involving a contrib module, I found that the same problem can be seen when rendering comment approval links (which also require a CSRF token) in a View.

Steps to reproduce the problem using comment approval links:

  1. Install Drupal with Standard install profile.
  2. Install the following the RESTful web services and Serialization modules
  3. Visit /node/add/article and add a new Article node
  4. Add a new comment to the Article
  5. Edit the comment and set the Status to Not Published
  6. Visit /admin/structure/views/add to create a new View
    1. Show: Comments
    2. Create a Block
    3. Provide REST export
    4. REST export path: my-api
    5. Click: Save and edit
    6. Edit the Block display
      1. Remove all Filters
      2. Show: Fields
      3. Add Field: Link to approve comment
    7. Edit the REST export display
      1. Format - Serializer Settings: json
      2. Show: Fields
    8. Save the View
  7. Testing results
    1. Preview the Block display and note the CSRF token value in the approve link: (looks like: token=E0yCjIOD0AWQtp3nOYdO-RMoSrhRYC2-GF_7v616wKg)
    2. Preview the REST export display and note the CSRF token value in the approve link: (looks like: token=6kNQYpaNjaFUdToL0eHPM_AQCfeosXpocZiSzmLFCBE)
    3. Attempt to visit the URL provided by the REST export approve link: /comment/1/approve?token=6kNQYpaNjaFUdToL0eHPM_AQCfeosXpocZiSzmLFCBE

      Outcome: You will see the "Access Denied" page.
    4. Attempt to visit the URL provided by the Block approve link: /comment/1/approve?token=E0yCjIOD0AWQtp3nOYdO-RMoSrhRYC2-GF_7v616wKg

      Outcome: You will be redirected to the node with a message that says "Comment approved."

I'm not positive this is the right track, but I dug into the various Views Display plugins and found a difference in the order of rendering between the RestExport display plugin and the DisplayPluginBase.

\Drupal\views\Plugin\views\display\DisplayPluginBase::render applies the cacheable metadata, and then returns a render array containing theme functions and other details about the output. This defers the actual rendering of the fields until the very end, after the cache metadata has been applied.

See \Drupal\views\Plugin\views\display\DisplayPluginBase::render

The order of operations for rendering a field using the DisplayPluginBase looks like this:

  1. Style::render
  2. Row::render
  3. Display::render (cache metadata applied)
  4. Field::render

Whereas the \Drupal\rest\Plugin\views\display\RestExport::render method completely renders the fields first, and then applies cache metadata.

See \Drupal\rest\Plugin\views\display\RestExport::render

The order of operations for rendering a field using the RestExport display looks like this:

  1. FieldRow::render
  2. Style::render
  3. Display::render (cache metadata applied)

Proposed resolution

Seems that the final rendering of fields should be deferred until after all cache metadata is applied, but this is just a guess. Someone more familiar with all the mechanisms at play here may know better.

Remaining tasks

Unsure.

User interface changes

Probably none.

API changes

Ideally if this is fixed it wouldn't affect any existing APIs or the output of sites that already make use of the REST export display plugin.

Manual testing short cut

Here is an export of the View this issue describes for testing:

uuid: 0bcb135e-3873-4a12-a453-2f63a86538c0
langcode: en
status: true
dependencies:
  module:
    - comment
    - node
    - rest
    - serialization
    - user
id: test_rest_csrf
label: test-rest-csrf
module: views
description: ''
tag: ''
base_table: comment_field_data
base_field: cid
core: 8.x
display:
  default:
    display_plugin: default
    id: default
    display_title: Master
    position: 0
    display_options:
      access:
        type: perm
        options:
          perm: 'access comments'
      cache:
        type: tag
        options: {  }
      query:
        type: views_query
        options:
          disable_sql_rewrite: false
          distinct: false
          replica: false
          query_comment: ''
          query_tags: {  }
      exposed_form:
        type: basic
        options:
          submit_button: Apply
          reset_button: false
          reset_button_label: Reset
          exposed_sorts_label: 'Sort by'
          expose_sort_order: true
          sort_asc_label: Asc
          sort_desc_label: Desc
      pager:
        type: some
        options:
          items_per_page: 5
          offset: 0
      style:
        type: default
      row:
        type: fields
        options:
          default_field_elements: true
          inline: {  }
          separator: ''
          hide_empty: false
      relationships:
        node:
          id: node
          table: comment_field_data
          field: node
          entity_type: comment_field_data
          required: true
          plugin_id: standard
          relationship: none
          group_type: group
          admin_label: Content
      fields:
        approve_comment:
          id: approve_comment
          table: comment
          field: approve_comment
          relationship: none
          group_type: group
          admin_label: ''
          label: ''
          exclude: false
          alter:
            alter_text: false
            text: ''
            make_link: false
            path: ''
            absolute: false
            external: false
            replace_spaces: false
            path_case: none
            trim_whitespace: false
            alt: ''
            rel: ''
            link_class: ''
            prefix: ''
            suffix: ''
            target: ''
            nl2br: false
            max_length: 0
            word_boundary: true
            ellipsis: true
            more_link: false
            more_link_text: ''
            more_link_path: ''
            strip_tags: false
            trim: false
            preserve_tags: ''
            html: false
          element_type: ''
          element_class: ''
          element_label_type: ''
          element_label_class: ''
          element_label_colon: false
          element_wrapper_type: ''
          element_wrapper_class: ''
          element_default_classes: true
          empty: ''
          hide_empty: false
          empty_zero: false
          hide_alter_empty: true
          text: Approve
          entity_type: comment
          plugin_id: comment_link_approve
      filters: {  }
      sorts: {  }
      title: test-rest-csrf
      header: {  }
      footer: {  }
      empty: {  }
      arguments: {  }
      display_extenders: {  }
      filter_groups:
        operator: AND
        groups: {  }
    cache_metadata:
      max-age: -1
      contexts:
        - 'languages:language_interface'
        - user.permissions
      tags: {  }
  block_1:
    display_plugin: block
    id: block_1
    display_title: Block
    position: 1
    display_options:
      display_extenders: {  }
    cache_metadata:
      max-age: -1
      contexts:
        - 'languages:language_interface'
        - user.permissions
      tags: {  }
  rest_export_1:
    display_plugin: rest_export
    id: rest_export_1
    display_title: 'REST export'
    position: 2
    display_options:
      display_extenders: {  }
      path: my-api
      style:
        type: serializer
        options:
          formats:
            json: json
      defaults:
        style: false
        row: false
      row:
        type: data_field
        options:
          field_options:
            subject:
              alias: ''
              raw_output: false
            approve_comment:
              alias: ''
              raw_output: false
      pager:
        type: some
        options:
          items_per_page: 10
          offset: 0
      auth: {  }
    cache_metadata:
      max-age: -1
      contexts:
        - 'languages:language_interface'
        - request_format
        - user.permissions
      tags: {  }
πŸ› Bug report
Status

Postponed: needs info

Version

11.0 πŸ”₯

Component

render system

Created by

πŸ‡ΊπŸ‡ΈUnited States daggerhart

Live updates comments and jobs are added and updated live.
  • VDC

    Related to the Views in Drupal Core initiative.

Sign in to follow issues

Comments & Activities

Not all content is available!

It's likely this issue predates Contrib.social: some issue and comment data are missing.

  • πŸ‡¦πŸ‡ΊAustralia acbramley

    I followed the steps in the IS, both creating my own view and testing the sample config provided and neither view had the issue. I was able to copy the comment approve URL from the rest export (and remove the destination param and faulty encoded &) and the comment was approved successfully.

    Looks like this may have been fixed in the past 5 years. Can anyone else confirm?

Production build 0.71.5 2024