WordPress / gutenberg

The Block Editor project for WordPress and beyond. Plugin is available from the official repository.
https://wordpress.org/gutenberg/
Other
10.48k stars 4.18k forks source link

Custom Preview Links not fully filterable #13998

Closed yoadsn closed 3 months ago

yoadsn commented 5 years ago

I am opening this to point at #4555. I believe the problem is not solved and should be reopened. All details are in my last comment on that thread.

corywebb commented 5 years ago

Completely agree. The preview links should be fully filterable with the preview_post_link filter. I'm not sure why that went away, or why the "autosaves" solution was considered sufficient.

draganescu commented 5 years ago

Hi there, I'll close this and reopen #4555 and we can continue the discussion there.

draganescu commented 5 years ago

Reopened the issue for @danielbachhuber and @yoadsn, see #4555

yoadsn commented 5 years ago

@danielbachhuber Recap of the problem analysis as described on #4555.

As noted - preview_post_link is useful and should be backward compatible using Gutenberg. I believe the fix in https://core.trac.wordpress.org/ticket/44180 is not complete.

Here is the scenario I am facing and my analysis.

  1. WP admin is accessed and used from domain A

  2. WP frontend is on domain B using reverse proxy - this is very common when wanting to host WP on a sub folder of a TLD (But this senario I believe applies also to any case where home_url !== site_url or in fact anytime the preview url needs to be filterable)

  3. Using Gutenberg for a draft post.

  4. After an autosave - the autosave endpoint return a preview_url as you have mentioned above. The reducer here would take the preview_link as is and use that for the preview button. As you have shown on the screenshots of the PR #6682 - hovering over the "preview" button at this point would show the url as it was returned from the autosave endpoint. This URL was generated here and using the get_preview_post_link which makes the preview link filterable.

  5. If now the "preview" button is clicked or the "save" link is clicked. The clien would hit the posts controller and get the payload which includes the post's link. This link is generated here and is using the get_permalink function. The permalink is filterable but in my scenario preview link need to work on site_url and not the home_url - permalinks should always be on home_url. (for the preview to work, all admin auth is against site_url). The reason this works is that the reducer mentioned above applies the { preview: true } query param on the client. This means there is an inconsistent behaviour between the way preview links are generated form the autosave controller and on the client.

Anyway - hovering over the button would now show a different URL (assuming site_url !== home_url).

I hope I analyzed the situation correctly - I'm jst a user of WP so first time looking at the source codes.

I might be able to work around this problem if I will filter the permalink and somehow know that it is going to be used on the "editor" and not on the frontend - but this is not possible since the posts controller does not identify itself to the call in anyway.

Thoughts?

Guibod commented 5 years ago

That's a really painful bug, and suffer from a similar use case described by @yoadsn . We use a reverse proxy to serve our legacy app and wordpress on the same domain. We need a specific url starting with a prefix that can be used by AWS Cloudfront to select the proper backend.

I'm really looking forward a solution.

danielbachhuber commented 5 years ago

@yoadsn Sorry for the late reply here. Your analysis seems reasonable although I'm not quite sure what the fix should be.

yoadsn commented 5 years ago

@danielbachhuber Of course I may be talking none-sense here but I would keep the preview link generation logic on the backend as much as possible and ensure it uses standard filterable generation methods like get_preview_post_link.

So, when hitting the "posts" controller - perhaps there will be a way to also ask for the "preview link" on top of the "link" (=permalink). This way, the client reducer can use that returned value instead of generating a preview link indirectly from the permalink.

I know the request can contain a fields parameter but I'm unsure how easy it is to extend the schema for a post to include such optional field.

So in the controller - similar to this field data prep:

if ( in_array( 'link', $fields, true ) ) {
  $data['link'] = get_permalink( $post->ID );
}

There will be: (Excuse my lack of source knowledge here - waving hands)

if ( in_array( 'preview_link', $fields, true ) ) {
  $parent_id          = wp_is_post_autosave( $post );
  $preview_post_id    = false === $parent_id ? $post->ID : $parent_id;
  $preview_query_args = array();
  if ( false !== $parent_id ) {
    $preview_query_args['preview_id']    = $parent_id;
    $preview_query_args['preview_nonce'] = wp_create_nonce( 'post_preview_' . $parent_id );
  }
  $data['preview_link'] = get_preview_post_link( $preview_post_id , $preview_query_args );
}

Quite similar to how the autosaves controller does it.

Hopefully this makes sense - reimplementing the link filtering on the client just sounds crazy to me and a bad call in terms of business logic encapsulation.

danielbachhuber commented 5 years ago

I would keep the preview link generation logic on the backend as much as possible and ensure it uses standard filterable generation methods like get_preview_post_link.

I agree with this.

So, when hitting the "posts" controller - perhaps there will be a way to also ask for the "preview link" on top of the "link" (=permalink). This way, the client reducer can use that returned value instead of generating a preview link indirectly from the permalink.

Right. The challenge is that I'm not sure, conceptually, how well this fits with the Posts Controller. Currently, the preview_link is the responsibility of the Autosaves Controller, which makes more conceptual sense — you're previewing a work-in-progress version of the post. At first glance, it doesn't seem like this fits cleanly with the Posts Controller.

But, maybe the simplest thing to do is simply add preview_link to the Posts Controller and be done with it.

@aduth or @azaozz have spent a ton more time in this area and might have other thoughts.

yoadsn commented 5 years ago

Makes sense. My 2 cents - Conceptually this API divides responsibility between "AutoSave" and "Save". Although differences do exist, the use case is the same - modifying post state. So if I would think this through I think I arrive at a conclusion that "Auto" is in fact a client concern and not a server concern. The Server API should have never indicate any difference between the two but rather facilitate either state mutations (Before publish and after publish) regardless of "auto" having . any meaning.

Given this is how the API is architected - Probably best to extend the post controller then overload the "autosave" controller with generic "saving" and "loading" of documents in any state.

As a bonus, the server can decide a "preview link" to a published version is just "the link" without the client caring.

aduth commented 5 years ago

There's a fair bit more context to the decision of creating a separate autosaves endpoint at:

https://core.trac.wordpress.org/ticket/43316#comment:62

...including considerations around revisions and the capabilities of the server in handling fields therein.

In Gutenberg, it doesn't really make much a difference whether it be one or separate endpoints, as long as we could communicate intent of the nature of the save as being of this more transient sort (i.e. not necessarily needing a full revision history).

Related: https://github.com/WordPress/gutenberg/issues/9151#issuecomment-414811399

As far as actionability: It seems there's not much reluctance about including preview_link in the return response of a "full" post save (i.e. on the posts controller). If I recall correctly, this had also been the behavior in earlier implementations (#6882). May I suggest a next step be to propose this for consideration in an upcoming REST API meeting and/or create a Trac ticket to introduce this new field?

As far as I can tell, there would actually be no change required in Gutenberg for this to start being leveraged, since the current behavior is to use preview_link from a response when and if it's available:

https://github.com/WordPress/gutenberg/blob/5b3b3aba47049e025464d4942a3e3d742c7513cf/packages/editor/src/store/reducer.js#L762-L769

kyleramirez commented 5 years ago

Hello, I'm reading this thread and not sure what the next steps are. I rewrite preview links from: /?p=8792&preview=true to /previews/?p=8792&preview=true. For draft posts, this works from the posts index, but while editing, my hook for preview_post_link never gets called. Is there a way to do this?

ocean90 commented 5 years ago

To add the preview_link to the REST response you can use this snippet:

/**
 * Includes preview link in post data for a response.
 *
 * @param \WP_REST_Response $response The response object.
 * @param \WP_Post          $post     Post object.
 * @return \WP_REST_Response The response object.
 */
function my_include_preview_link_in_rest_response( $response, $post ) {
    if ( 'draft' === $post->post_status ) {
        $response->data['preview_link'] = get_preview_post_link( $post );
    }

    return $response;
}
add_filter( 'rest_prepare_post', 'my_include_preview_link_in_rest_response', 10, 2 );
add_filter( 'rest_prepare_page', 'my_include_preview_link_in_rest_response', 10, 2 );

See also https://core.trac.wordpress.org/ticket/44180 for a similar version.

As far as I can tell, there would actually be no change required in Gutenberg for this to start being leveraged, since the current behavior is to use preview_link from a response when and if it's available:

@aduth You're correct. The only thing is that REQUEST_POST_UPDATE_SUCCESS isn't called for the initial editor load when the data is preloaded. Wondering if we should change that.

martinszeltins commented 5 years ago

There's still no filter to change the Preview link that is on the right side of Gutenberg.

Jamiewarb commented 4 years ago

Is there any news on this at all? :)

tvanro commented 4 years ago

I'm having the same problem and """solved""" it with a workaround (notice the triple double quotes). Maybe it could help some people until there's an official solution. Feel free to use and/or improve this workaround.

Add this in your theme's functions.php

// workaround script until there's an official solution for https://github.com/WordPress/gutenberg/issues/13998
function fix_preview_link_on_draft() {
    echo '<script type="text/javascript">
        jQuery(document).ready(function () {
            const checkPreviewInterval = setInterval(checkPreview, 1000);
            function checkPreview() {
                const editorPreviewButton = jQuery(".editor-post-preview");
                const editorPostSaveDraft = jQuery(".editor-post-save-draft");
                if (editorPostSaveDraft.length && editorPreviewButton.length && editorPreviewButton.attr("href") !== "' . get_preview_post_link() . '" ) {
                    editorPreviewButton.attr("href", "' . get_preview_post_link() . '");
                    editorPreviewButton.off();
                    editorPreviewButton.click(false);
                    editorPreviewButton.on("click", function() {
                        editorPostSaveDraft.click();
                        setTimeout(function() { 
                            const win = window.open("' . get_preview_post_link() . '", "_blank");
                            if (win) {
                                win.focus();
                            }
                        }, 1000);
                    });
                }
            }
        });
    </script>';
}
add_action('admin_footer', 'fix_preview_link_on_draft');

The script is running every second as Gutenberg also updates the preview URL on certain actions. Each interval it will execute the following logic:

Use at your own risk! It might break on future Gutenberg versions

Tested on Wordpress 5.3.2

ciprianimike commented 4 years ago

any news on this issue?

carlotrimarchi commented 4 years ago
function fix_preview_link_on_draft() {
  echo '<script type="text/javascript">
      jQuery(document).ready(function () {
          const checkPreviewInterval = setInterval(checkPreview, 1000);
          function checkPreview() {
              const editorPreviewButton = jQuery(".editor-post-preview");
              const editorPostSaveDraft = jQuery(".editor-post-save-draft");
              if (editorPostSaveDraft.length && editorPreviewButton.length && editorPreviewButton.attr("href") !== "' . get_preview_post_link() . '" ) {
                  editorPreviewButton.attr("href", "' . get_preview_post_link() . '");
                  editorPreviewButton.off();
                  editorPreviewButton.click(false);
                  editorPreviewButton.on("click", function() {
                      editorPostSaveDraft.click();
                      setTimeout(function() { 
                          const win = window.open("' . get_preview_post_link() . '", "_blank");
                          if (win) {
                              win.focus();
                          }
                      }, 1000);
                  });
              }
          }
      });
  </script>';
}
add_action('admin_footer', 'fix_preview_link_on_draft');

Use at your own risk! It might break on future Gutenberg versions

Tested on Wordpress 5.3.2

thanks @tvanro , your solution is the only one that has worked for me so far.

pixelprogrammer commented 4 years ago

Thanks @tvanro that got me out of a pickle I updated the code so it works with new posts and set an interval when clicked so that the window opens when the post is saved. I also moved the check for the draft button inside the click function because it may not exist on first visit with no updates. So this gives the user the ability to click preview when first visiting the edit screen for the post.

jQuery(document).ready(function () {
    const checkPreviewInterval = setInterval(checkPreview, 1000);
    function checkPreview() {
        const editorPreviewButton = jQuery(".editor-post-preview");

        if (editorPreviewButton.length && editorPreviewButton.attr("href") !== "' . get_preview_post_link() . '" ) {
            editorPreviewButton.attr("href", "' . get_preview_post_link() . '");
            editorPreviewButton.off();
            editorPreviewButton.click(false);
            editorPreviewButton.on("click", function(e) {
                const editorPostSaveDraft = jQuery(".editor-post-save-draft");

                if(editorPostSaveDraft.length > 0) {
                    editorPostSaveDraft.click();
                }
                const intervalId = setInterval(function() {
                    // find out when the post is saved
                    let saved = document.querySelector(".is-saved");
                    if(saved) {
                        clearInterval(intervalId);
                        const win = window.open("' . get_preview_post_link() . '", "_blank");
                        if (win) {
                            win.focus();
                        }
                    }
                }, 50);
            });
        }
    }
});
joaokrabbe commented 4 years ago

Thanks @tvanro. Save me a lot of time.

Morgunov-Vitaly commented 4 years ago

We are still waiting for native solution of the isue..

ermauliks commented 4 years ago

I'm having the same problem and """solved""" it with a workaround (notice the triple double quotes). Maybe it could help some people until there's an official solution. Feel free to use and/or improve this workaround.

Add this in your theme's functions.php

// workaround script until there's an official solution for https://github.com/WordPress/gutenberg/issues/13998
function fix_preview_link_on_draft() {
  echo '<script type="text/javascript">
      jQuery(document).ready(function () {
          const checkPreviewInterval = setInterval(checkPreview, 1000);
          function checkPreview() {
              const editorPreviewButton = jQuery(".editor-post-preview");
              const editorPostSaveDraft = jQuery(".editor-post-save-draft");
              if (editorPostSaveDraft.length && editorPreviewButton.length && editorPreviewButton.attr("href") !== "' . get_preview_post_link() . '" ) {
                  editorPreviewButton.attr("href", "' . get_preview_post_link() . '");
                  editorPreviewButton.off();
                  editorPreviewButton.click(false);
                  editorPreviewButton.on("click", function() {
                      editorPostSaveDraft.click();
                      setTimeout(function() { 
                          const win = window.open("' . get_preview_post_link() . '", "_blank");
                          if (win) {
                              win.focus();
                          }
                      }, 1000);
                  });
              }
          }
      });
  </script>';
}
add_action('admin_footer', 'fix_preview_link_on_draft');

The script is running every second as Gutenberg also updates the preview URL on certain actions. Each interval it will execute the following logic:

  • Check if we have .editor-post-preview and .editor-post-save-draft and that the href is not the same as get_preview_post_link()
  • If yes -> continue with the next steps || if no -> finish
  • Update href with the correct get_preview_post_link()
  • Remove the listener events added by Gutenberg from the button
  • Add our listener event that will save the draft and then open get_preview_post_link() in a new tab on click

Use at your own risk! It might break on future Gutenberg versions

Tested on Wordpress 5.3.2

Modified this function so each preview click doesn't open the new tab đź‘Ť

setTimeout(function() {
    const win = window.open("' . get_preview_post_link() . '", editorPreviewButton.attr("target"));
    if (typeof win.name === "undefined") win.name = editorPreviewButton.attr("target");
    if (win) {
        win.focus();
    }
}, 1500);
malahmen commented 4 years ago

I'm having the same problem and """solved""" it with a workaround (notice the triple double quotes). Maybe it could help some people until there's an official solution. Feel free to use and/or improve this workaround. Add this in your theme's functions.php

// workaround script until there's an official solution for https://github.com/WordPress/gutenberg/issues/13998
function fix_preview_link_on_draft() {
    echo '<script type="text/javascript">
        jQuery(document).ready(function () {
            const checkPreviewInterval = setInterval(checkPreview, 1000);
            function checkPreview() {
                const editorPreviewButton = jQuery(".editor-post-preview");
                const editorPostSaveDraft = jQuery(".editor-post-save-draft");
                if (editorPostSaveDraft.length && editorPreviewButton.length && editorPreviewButton.attr("href") !== "' . get_preview_post_link() . '" ) {
                    editorPreviewButton.attr("href", "' . get_preview_post_link() . '");
                    editorPreviewButton.off();
                    editorPreviewButton.click(false);
                    editorPreviewButton.on("click", function() {
                        editorPostSaveDraft.click();
                        setTimeout(function() { 
                            const win = window.open("' . get_preview_post_link() . '", "_blank");
                            if (win) {
                                win.focus();
                            }
                        }, 1000);
                    });
                }
            }
        });
    </script>';
}
add_action('admin_footer', 'fix_preview_link_on_draft');

The script is running every second as Gutenberg also updates the preview URL on certain actions. Each interval it will execute the following logic:

  • Check if we have .editor-post-preview and .editor-post-save-draft and that the href is not the same as get_preview_post_link()
  • If yes -> continue with the next steps || if no -> finish
  • Update href with the correct get_preview_post_link()
  • Remove the listener events added by Gutenberg from the button
  • Add our listener event that will save the draft and then open get_preview_post_link() in a new tab on click

Use at your own risk! It might break on future Gutenberg versions Tested on Wordpress 5.3.2

Modified this function so each preview click doesn't open the new tab đź‘Ť

setTimeout(function() {
    const win = window.open("' . get_preview_post_link() . '", editorPreviewButton.attr("target"));
    if (typeof win.name === "undefined") win.name = editorPreviewButton.attr("target");
    if (win) {
        win.focus();
    }
}, 1500);

Well, in order to not spread this all over the backend pages, I've used these hooks/actions instead:

add_action( 'admin_footer-edit.php', 'fix_preview_link_on_draft' ); // Fired on the page with the posts table add_action( 'admin_footer-post.php', 'fix_preview_link_on_draft' ); // Fired on post edit page add_action( 'admin_footer-post-new.php', 'fix_preview_link_on_draft' ); // Fired on add new post page

from here.

MoOx commented 3 years ago

Instead of waiting to change this preview links and since this bug doesn't trigger enough traction despite being an issue for several years, my approach is the dumbest I could find, but it work for all kind of preview (unpublished or not)!

In my function.php I brutally catch all preview links to show an iframe from the frontend (and I inject and authToken so my author doesn't have to make any login from the frontend etc).

if (!is_admin() && isset($_GET["preview"]) && $_GET["preview"]==true) {
  $auth = new \WPGraphQL\JWT_Authentication\Auth;
  $authToken = $auth->get_token(wp_get_current_user());
  $url = add_query_arg([
    'id' => isset($_GET["p"]) ? $_GET["p"] : $_GET["preview_id"],
    'secret' => "cestsecretvoilĂ tout",
    'authToken' => $authToken
  ], FRONTEND_URL . '/api/preview');

  echo '<iframe src="' . $url . '" style="width:100%; height: 100%;">';
  exit(1);
}

Note: My setup is next.js + apollo / wpgraphql on the frontend.

ntsim commented 3 years ago

Instead of waiting to change this preview links and since this bug doesn't trigger enough traction despite being an issue for several years, my approach is the dumbest I could find, but it work for all kind of preview (unpublished or not)!

In my function.php I brutally catch all preview links to show an iframe from the frontend (and I inject and authToken so my author doesn't have to make any login from the frontend etc).

if (!is_admin() && isset($_GET["preview"]) && $_GET["preview"]==true) {
  $auth = new \WPGraphQL\JWT_Authentication\Auth;
  $authToken = $auth->get_token(wp_get_current_user());
  $url = add_query_arg([
    'id' => isset($_GET["p"]) ? $_GET["p"] : $_GET["preview_id"],
    'secret' => "cestsecretvoilĂ tout",
    'authToken' => $authToken
  ], FRONTEND_URL . '/api/preview');

  echo '<iframe src="' . $url . '" style="width:100%; height: 100%;">';
  exit(1);
}

Note: My setup is next.js + apollo / wpgraphql on the frontend.

This is actually genius, thank you for this! This works a lot better than other solutions I've seen around previewing :grimacing:

I would just add that rather than doing this in functions.php, you can do it in the singular.php template. Think it ends up being a bit cleaner as you can also call wp_head and wp_footer which gives you the WordPress admin bar, making it a lot more like the native admin experience.

amjadgoldev commented 3 years ago

for wp Version 5.7.2

jQuery(document).ready(function () { const checkPreviewInterval = setTimeout(checkPreview, 1000); //checkPreview(); function checkPreview() { const ToggleButton = jQuery(".block-editor-post-preview__button-toggle"); const editorPreviewButton = jQuery(".editor-post-preview"); ToggleButton.hide(); editorPreviewButton.show(); if (editorPreviewButton.length && editorPreviewButton.attr("href") !== "' . get_preview_post_link() . '" ) { editorPreviewButton.attr("href", "' . get_preview_post_link() . '"); editorPreviewButton.off(); editorPreviewButton.click(false); editorPreviewButton.on("click", function() { setTimeout(function() { const win = window.open("' . get_preview_post_link() . '", "_blank"); if (win) { win.focus(); } }, 1000); }); } } });

dextorlobo commented 3 years ago

Instead of waiting to change this preview links and since this bug doesn't trigger enough traction despite being an issue for several years, my approach is the dumbest I could find, but it work for all kind of preview (unpublished or not)!

In my function.php I brutally catch all preview links to show an iframe from the frontend (and I inject and authToken so my author doesn't have to make any login from the frontend etc).

if (!is_admin() && isset($_GET["preview"]) && $_GET["preview"]==true) {
  $auth = new \WPGraphQL\JWT_Authentication\Auth;
  $authToken = $auth->get_token(wp_get_current_user());
  $url = add_query_arg([
    'id' => isset($_GET["p"]) ? $_GET["p"] : $_GET["preview_id"],
    'secret' => "cestsecretvoilĂ tout",
    'authToken' => $authToken
  ], FRONTEND_URL . '/api/preview');

  echo '<iframe src="' . $url . '" style="width:100%; height: 100%;">';
  exit(1);
}

Note: My setup is next.js + apollo / wpgraphql on the frontend.

This solution works fine for me. But I have done some modifications to get the latest revision ID of the custom post type (article). This work is for the draft and published posts. I am not sure, it might need some changes but it works for me.

/**
 * Redirect preview link of the post to front-end domain.
 */
add_action(
    'init',
    function() {
        if (
            ! is_admin() &&
            isset( $_GET['preview'] ) &&
            true == $_GET['preview']
        ) {
            if ( isset( $_GET['p'] ) ) {
                $post_id = intval( $_GET['p'] ); // Newly created post with draft status.
            }
            if ( isset( $_GET['preview_id'] ) ) {
                $post_id = intval( $_GET['preview_id'] ); // Published post.
            }
            $post = get_post( $post_id );
            if ( 'article' !== $post->post_type ) {
                return;
            }
            $revisions   = wp_get_post_revisions( $post_id );
            $revision    = reset( $revisions );
            $preview_url = get_front_end_domain() . 'preview/' . $post_id . '/version/' . $revision->ID;
            wp_redirect( $preview_url );
            exit();
        }
    }
);

I have to get the different domains for each environment (local, develop, staging, production). So I have created the get_front_end_domain() method to fetch the current environment domain.

benknight commented 3 years ago

My own version of @MoOx's fix. It's better to avoid using a fullscreen iframe and just do a redirect:

// Redirect preview page to Next preview
add_action("template_redirect", function () {
    if (!is_admin() && isset($_GET["preview"]) && $_GET["preview"] == true) {
        $redirect = add_query_arg(
            [
                "redirect" => urlencode("/post?p=" . $_GET["p"]),
                "secret" => "abcdef123456",
            ],
            "https://www.example.com/api/preview"
        );
        wp_redirect($redirect);
    }
});
emanuellopes commented 2 years ago

I've used the old approach adding a .js file on code.

This time I've tried another solution, I think this is better than use the .js script.

<?php

namespace App\Routes;

class PreviewArticle
{
    public function __construct()
    {
        $this->preview_post();
    }

    /**
     * Add filter to modify the link for preview post
     */
    private function preview_post()
    {
        add_filter('preview_post_link', array($this, 'preview_post_link_filter'), 1, 2);
        add_filter('rest_prepare_post', array($this, 'hack_fix_preview_link'), 10, 2);
    }

    /**
     * Callback function that changes the preview link for post
     *
     * @param $preview_link
     * @param $post
     *
     * @return string
     */
    public function preview_post_link_filter($preview_link, $post): string
    {
        $postID = ($post) ? $post->ID : 0;
        try {
            $token = createJWTToken($postID);
        } catch (UserException $exception) {
            Logger::info("Error Creating JWT token for Preview Page");

            return $preview_link;
        }

        return sprintf('%s/preview/%s?token=%s', home_url(), $post->ID, $token);
    }

    /**
     * Hack Function that changes the preview link for draft articles,
     * this must be removed when wordpress do the properly fix https://github.com/WordPress/gutenberg/issues/13998
     *
     * @param $response
     * @param $post
     *
     * @return mixed
     */
    function hack_fix_preview_link($response, $post)
    {
        if ('draft' === $post->post_status) {
            $response->data['link'] = get_preview_post_link($post);
        }

        return $response;
    }
}
oseisaac commented 2 years ago

Instead of waiting to change this preview links and since this bug doesn't trigger enough traction despite being an issue for several years, my approach is the dumbest I could find, but it work for all kind of preview (unpublished or not)!

In my function.php I brutally catch all preview links to show an iframe from the frontend (and I inject and authToken so my author doesn't have to make any login from the frontend etc).

if (!is_admin() && isset($_GET["preview"]) && $_GET["preview"]==true) {
  $auth = new \WPGraphQL\JWT_Authentication\Auth;
  $authToken = $auth->get_token(wp_get_current_user());
  $url = add_query_arg([
    'id' => isset($_GET["p"]) ? $_GET["p"] : $_GET["preview_id"],
    'secret' => "cestsecretvoilĂ tout",
    'authToken' => $authToken
  ], FRONTEND_URL . '/api/preview');

  echo '<iframe src="' . $url . '" style="width:100%; height: 100%;">';
  exit(1);
}

Note: My setup is next.js + apollo / wpgraphql on the frontend.

Where do I add this code to make it work

ltroya-as commented 2 years ago

I've used the old approach adding a .js file on code.

This time I've tried another solution, I think this is better than use the .js script.

<?php

namespace App\Routes;

class PreviewArticle
{
    public function __construct()
    {
        $this->preview_post();
    }

    /**
     * Add filter to modify the link for preview post
     */
    private function preview_post()
    {
        add_filter('preview_post_link', array($this, 'preview_post_link_filter'), 1, 2);
        add_filter('rest_prepare_post', array($this, 'hack_fix_preview_link'), 10, 2);
    }

    /**
     * Callback function that changes the preview link for post
     *
     * @param $preview_link
     * @param $post
     *
     * @return string
     */
    public function preview_post_link_filter($preview_link, $post): string
    {
        $postID = ($post) ? $post->ID : 0;
        try {
            $token = createJWTToken($postID);
        } catch (UserException $exception) {
            Logger::info("Error Creating JWT token for Preview Page");

            return $preview_link;
        }

        return sprintf('%s/preview/%s?token=%s', home_url(), $post->ID, $token);
    }

    /**
     * Hack Function that changes the preview link for draft articles,
     * this must be removed when wordpress do the properly fix https://github.com/WordPress/gutenberg/issues/13998
     *
     * @param $response
     * @param $post
     *
     * @return mixed
     */
    function hack_fix_preview_link($response, $post)
    {
        if ('draft' === $post->post_status) {
            $response->data['link'] = get_preview_post_link($post);
        }

        return $response;
    }
}

Where do you put this code? @emanuellopes

emanuellopes commented 2 years ago
$_GET["p"]

Where do you put this code? @emanuellopes

Can you be more specific?

Don't use $_GET variables directly!! You must filter the variables.

emanuellopes commented 2 years ago

you can put this code on funtions.php like this

new App\Routes\PreviewArticle();

don't forget to use composer to autoload the file

bph commented 3 months ago

This seems to be an edge case only applicable to headless implementation. Developers provided a few work around code examples. The issue hasn't seen any additional comments for the last 1 1/2 years. Let's close it.

rodrigo-arias commented 3 months ago

There are indeed workarounds to achieve this, but in my opinion, it's not an edge case. Most headless implementations require this functionality.