Skip to content

Commit

Permalink
Merge branch 'main' into 174-seo
Browse files Browse the repository at this point in the history
* main:
  feat: log wp admin users into front-end preview mode (#173)
  • Loading branch information
ccorda committed Nov 26, 2021
2 parents dad0317 + cd143c4 commit d961a51
Show file tree
Hide file tree
Showing 12 changed files with 177 additions and 33 deletions.
26 changes: 21 additions & 5 deletions docs/preview-mode.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,31 @@ Our goal with implementing this was to use built in Next.js and WordPress functi

# Setup Instructions

1. On your production/staging WordPress (which isn't versioned and on WP Engine requires SFTP/SSH) open `wp-config.php`, scroll to the bottom, and add two variables which help configure preview mode. These are used to help secure the authentication tokens therefore need to be randomly generated for each project. [You can get a strong random key from GRC's passwords page.](https://www.grc.com/passwords.htm) Make sure both values are different.
## Production Instance

1. On your production/staging WordPress `wp-config.php`, scroll to the bottom, and add two variables which help configure preview mode. These are used to help secure the authentication tokens therefore need to be randomly generated for each project. [You can get a strong random key from GRC's passwords page.](https://www.grc.com/passwords.htm) Make sure both values are different.

```
define('HEADLESS_AUTH_SECRET', 'bubs-next-wp-auth-secret-key');
define('HEADLESS_API_SECRET', 'bubs-next-headless-secret-key');
```

2. Save `HEADLESS_API_SECRET` to Vercel as an environment variable, this is needed to authenticate API calls to WordPress to securely generate the logged in user's access token to activate preview mode. `HEADLESS_AUTH_SECRET` only lives inside WordPress to encrypt the access token, DO NOT copy/use this value outside of the setting in `wp-config.php`. Set `WORDPRESS_DOMAIN` to the root URL of the Wordpress instance (without /graphql)
_On WP Engine, these values aren't version controlled so you'll need to SFTP or SSH in to work on them_

2. Save `HEADLESS_API_SECRET` to Vercel as an environment variable, this is needed to authenticate API calls to WordPress to securely generate the logged in user's access token to activate preview mode. `HEADLESS_AUTH_SECRET` only lives inside WordPress to encrypt the access token, DO NOT copy/use this value outside of the setting in `wp-config.php`.

3. Set `WORDPRESS_DOMAIN` to the root URL of the Wordpress instance (without /graphql)

4. Open `wordpress/wp-content/headless/functions.php` and edit the values for `$preview_domain` for production.

5. Preview mode should now work. You can test by logging into Wordpress admin, creating a post (but don't publish!) and then click "Preview". If it all works, you should be able to view your post with a black bar on the top of the page indicating preview mode is enabled.

## Preview Instances

By default, we tend to configure preview branches to use production graphql, so they can run against live data. This works great for previewing front-end changes.

Sometimes we want a staging WordPress where we can test changes there.

3. Open `wordpress/wp-content/headless/functions.php` and edit the values for `$preview_domain` for production.
Within staging, where we often use Vercel preview builds with different URLs, the Preview URL can be set in the Appearance > Customization section in the theme, under the "Headless" options heading.
Vercel supports per branch env variables, which allows us to setup a combination of WP backend and Next front-end for a specific branch. You'll want to set the `WORDPRESS_DOMAIN` and `WORDPRESS_API_URL` variables to point to your staging WordPress. The `HEADLESS_AUTH_SECRET` and `HEADLESS_API_SECRET` can be the same values on both production and staging.

4. Preview mode should now work. You can test by logging into Wordpress admin, creating a post (but don't publish!) and then click "Preview". If it all works, you should be able to view your post with a black bar on the top of the page indicating preview mode is enabled.
To tie together your staging WordPress with your preview frontend, you need to set one variable in WordPress. Inside of WordPress, go to Appearance > Customization section in the theme. Under the "Headless" options heading, you can set the URL for your preview instance.
20 changes: 14 additions & 6 deletions website/src/components/PreviewModeBar.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,21 @@
import cx from 'classnames';
import { WORDPRESS_DOMAIN } from 'lib/constants';
import { WORDPRESS_URL } from 'lib/constants';
import { useState, useEffect } from 'react';
import { BsInfoCircle } from 'react-icons/bs';
import styles from './PreviewModeBar.module.scss';

const WORDPRESS_EDIT_URL =
process.env.WORDPRESS_EDIT_URL ||
`https://${WORDPRESS_DOMAIN}/wp-admin/post.php?action=edit`;
`${WORDPRESS_URL}/wp-admin/post.php?action=edit`;

export default function PreviewModeBar({ postId, position = 'top' }) {
export default function PreviewModeBar({
postId,
position = 'bottom',
}) {
const [redirect, setRedirect] = useState('/api/exit-preview');
let positionClassName = styles['top'];

if ( position === 'bottom' ) {
if (position === 'bottom') {
positionClassName = styles['bottom'];
}

Expand All @@ -30,9 +34,13 @@ export default function PreviewModeBar({ postId, position = 'top' }) {
<div className="container">
<div className="row">
<div className="col text-center">
You are viewing this site in Preview Mode.{' '}
<span className={styles.icon}>
<BsInfoCircle />
</span>
You are viewing this site in Preview Mode
{postId && (
<>
&nbsp;&nbsp;|&nbsp;&nbsp;
<a
href={`${WORDPRESS_EDIT_URL}&post=${postId}`}
target="_blank"
Expand All @@ -43,7 +51,7 @@ export default function PreviewModeBar({ postId, position = 'top' }) {
&nbsp;&nbsp;|&nbsp;&nbsp;
</>
)}
<a href={redirect}>Exit Preview Mode</a>
<a href={redirect}>Exit</a>
</div>
</div>
</div>
Expand Down
15 changes: 15 additions & 0 deletions website/src/components/PreviewModeBar.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,19 @@
.bottom {
position: fixed;
bottom: 0;

@media (min-width: 576px) {
bottom: 20px;
left: 20px;
display: inline-block;
width: auto;
border-radius: 5px;
}
}

.icon {
display: inline-block;
margin-right: 4px;
font-size: 20px;
font-weight: bold;
}
2 changes: 1 addition & 1 deletion website/src/components/layouts/LayoutDefault.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export default function LayoutDefault({
image={image}
seo={seo}
/>
{preview && <PreviewModeBar postId={postId} position="top" />}
{preview && <PreviewModeBar postId={postId} />}
<Header />
{children}
<Footer />
Expand Down
5 changes: 5 additions & 0 deletions website/src/lib/auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ export async function authorize(code) {
if (!WORDPRESS_URL) {
throw Error('WORDPRESS_URL not defined');
}

if (!API_CLIENT_SECRET) {
throw Error('HEADLESS_API_SECRET not defined');
}

const response = await fetch(
`${WORDPRESS_URL}/wp-json/wpac/v1/authorize`,
{
Expand Down
11 changes: 6 additions & 5 deletions website/src/pages/[[...slug]].js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import { GlobalsProvider } from '../contexts/GlobalsContext';

export default function Page({
post,
postId,
preview,
isHome,
globals,
Expand Down Expand Up @@ -45,7 +44,7 @@ export default function Page({
<LayoutDefault
preview={preview}
seo={post?.seo}
postId={postId}
postId={post?.databaseId}
title={post?.title}
>
<Flex sections={flexSections} />
Expand All @@ -57,7 +56,7 @@ export default function Page({
return (
<GlobalsProvider globals={globals}>
<LayoutDefault
postId={postId}
postId={post?.databaseId}
seo={post?.seo}
preview={preview}
title={post?.title}
Expand Down Expand Up @@ -97,8 +96,10 @@ export async function getStaticProps({

const globals = await getGlobalProps();

if (Array.isArray(params.slug) && Array.isArray(globals?.redirection?.redirects) ) {

if (
Array.isArray(params.slug) &&
Array.isArray(globals?.redirection?.redirects)
) {
// check for redirect. remove trailing slashes from each to normalize
const redirect = globals?.redirection?.redirects?.find(
(row) =>
Expand Down
41 changes: 28 additions & 13 deletions website/src/pages/api/preview.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import { authorize } from 'lib/auth';
import { getContentTypes, getPreviewContent } from 'lib/wordpress';

const COOKIE_MAX_AGE = 86400;

export default async function preview(req, res) {
let accessToken;
const { code, id, preview_id, path, slug } = req.query;

if (!id) {
return res.status(401).json({ message: 'Invalid request' });
}

// Get Auth Token
if (req.previewData.token) {
accessToken = req.previewData.token;
} else if (code) {
Expand All @@ -18,6 +17,27 @@ export default async function preview(req, res) {
return res.status(401).json({ message: 'Invalid token' });
}

// This is an admin attempt to turn preview mode on, not a request for specific content
if (!id && !slug) {
res.setPreviewData(
{ token: accessToken },
{ maxAge: COOKIE_MAX_AGE },
);

if (path) {
res.writeHead(307, { Location: path });
} else {
res.writeHead(200);
}

return res.end();
}

// Get ID
if (!id) {
return res.status(401).json({ message: 'Invalid request' });
}

// Fetch WordPress to check if the provided `id` exists
const post = await getPreviewContent(
id,
Expand Down Expand Up @@ -50,7 +70,7 @@ export default async function preview(req, res) {
token: accessToken,
},
{
maxAge: 60 * 60,
maxAge: COOKIE_MAX_AGE,
},
);

Expand All @@ -67,16 +87,11 @@ export default async function preview(req, res) {
Location = `/${post.databaseId}`;
} else if (preview_id && post.contentType.node.name !== 'page') {
Location = `/${typePath}/${preview_id}`;
} else if (preview_id) {
Location = `/${preview_id}`;
} else if (slug) {
Location = `/${slug}`;
} else if (path) {
Location = `/${path}`;
} else if (preview_id || slug || path) {
Location = `/${preview_id || slug || path}`;
}

// Redirect to the path from the fetched post
res.writeHead(307, { Location });

res.end();
return res.end();
}
11 changes: 9 additions & 2 deletions website/src/pages/posts/[[...slug]].js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,11 @@ import { useRouter } from 'next/router';
function PostsSinglePage({ post, globals, preview }) {
return (
<GlobalsProvider globals={globals}>
<LayoutDefault title={post?.title} preview={preview}>
<LayoutDefault
title={post?.title}
preview={preview}
postId={post?.databaseId}
>
<div className="container">
<div className="row">
<div className="col-12">
Expand Down Expand Up @@ -62,7 +66,10 @@ export async function getStaticProps(context) {
//
const globals = await getGlobalProps();

if (Array.isArray(context.params.slug) && Array.isArray(globals?.redirection?.redirects)) {
if (
Array.isArray(context.params.slug) &&
Array.isArray(globals?.redirection?.redirects)
) {
const redirect = globals?.redirection?.redirects.find(
(row) => row.origin === `/posts/${context.params.slug[0]}/`,
);
Expand Down
5 changes: 4 additions & 1 deletion wordpress/wp-content/themes/headless/functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
$staging_wp_host = 'bubsnexts.wpengine.com';
$staging_headless_domain = 'https://bubs-next-git-staging-patronage.vercel.app';
$local_domain = 'http://localhost:3000';
$docs_link = ''; // set to a path if you have a site/document for editor instructions

// Determine the hosting environment we're in
if ( defined('WP_ENV') && WP_ENV == "development" ) {
Expand Down Expand Up @@ -58,9 +59,11 @@ function bubs_theme_options($wp_customize)
include_once 'setup/helpers/admin.php';
include_once 'setup/helpers/admin-env.php';
include_once 'setup/helpers/auth.php';
include_once 'setup/helpers/webhooks.php';
include_once 'setup/helpers/dashboard-preview.php';
include_once 'setup/helpers/headless-redirect.php';
include_once 'setup/helpers/menus.php';
include_once 'setup/helpers/permalinks.php';
include_once 'setup/helpers/webhooks.php';
include_once 'setup/helpers/wpgraphql.php';
include_once 'setup/helpers/wysiwyg.php';

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php

// remove widgets from the dashboard
// add a site links widget
// log WP users into the website front-end so they can view live content

add_action('wp_dashboard_setup', 'bubs_custom_dashboard_widgets');

function bubs_custom_dashboard_widgets() {
global $wp_meta_boxes;

// remove all
// $wp_meta_boxes['dashboard']['normal']['core'] = array();
// $wp_meta_boxes['dashboard']['side']['core'] = array();

// add our custom one
if (function_exists('build_preview_link')) {
wp_add_dashboard_widget('preview_mode_widget', 'Preview Mode', 'preview_mode_widget');
}
}

function preview_mode_widget() {
global $headless_domain;
global $docs_link;
?>
<div>
<p><strong>Site Links</strong></p>
<ul>
<li><a href="<?php echo $headless_domain; ?>">View Site</a></li>
<? echo $docs_link ? '<li><a href="' . $docs_link . '">View Docs</a></li>' : '' ?>
<li><a href="<?php echo build_preview_link(); ?>&path=/" target="_blank">Enable preview mode</a></li>
<li><a href="<?php echo $headless_domain ?>/api/exit-preview" target="_blank">Disable preview mode</a></li>
</ul>
</div>
<? if (function_exists('build_preview_link')) {
echo "<iframe src=\"" . build_preview_link() . "\" style=\"width:1px;height:1px;opacity:0;\"></iframe>";
}?>
<?php }
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
<?php


function build_preview_link() {
global $headless_domain;

$auth_code = wpe_headless_generate_authentication_code(
wp_get_current_user()
);

return $headless_domain . '/api/preview/?code=' . rawurlencode($auth_code);
}

function headless_redirect(){
global $headless_domain;

Expand Down
25 changes: 25 additions & 0 deletions wordpress/wp-content/themes/headless/setup/helpers/permalinks.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php
// This rewrites permalinks so they point to your front-end domain, not the headless WP
function bubs_next_preview_link( $link ) {
global $headless_domain;

return str_replace( trailingslashit( $headless_domain ), trailingslashit( get_home_url() ), $link );
}

function bubs_next_post_link( $link ) {
global $headless_domain;

if (function_exists('is_graphql_request') && is_graphql_request()) {
return $link;
} elseif ($headless_domain) {
return str_replace( trailingslashit( get_home_url() ), trailingslashit( $headless_domain ), $link );
} else {
return $link;
}
}

add_filter( 'page_link', 'bubs_next_post_link' );
add_filter( 'post_link', 'bubs_next_post_link' );
add_filter( 'term_link', 'bubs_next_post_link' );
add_filter( 'post_type_link', 'bubs_next_post_link' );
add_filter( 'preview_post_link', 'bubs_next_preview_link' );

0 comments on commit d961a51

Please sign in to comment.