Index: src/wp-admin/edit-form-advanced.php
===================================================================
--- src/wp-admin/edit-form-advanced.php	(revision 34105)
+++ src/wp-admin/edit-form-advanced.php	(working copy)
@@ -81,8 +81,7 @@
 }
 
 $messages = array();
-/** This filter is documented in wp-admin/includes/meta-boxes.php */
-$post_preview_url = apply_filters( 'preview_post_link', add_query_arg( 'preview', 'true', $permalink ), $post );
+$post_preview_url = get_preview_post_link( $post );
 
 $preview_link_html = $scheduled_link_html = $view_post_html = '';
 
@@ -125,8 +124,7 @@
 	10 => __( 'Post draft updated.' ) . $preview_link_html,
 );
 
-/** This filter is documented in wp-admin/includes/meta-boxes.php */
-$page_preview_url = apply_filters( 'preview_post_link', add_query_arg( 'preview', 'true', $permalink ), $post );
+$page_preview_url = get_preview_post_link( $post );
 
 $messages['page'] = array(
 	 0 => '', // Unused. Messages start at index 1.
Index: src/wp-admin/includes/ajax-actions.php
===================================================================
--- src/wp-admin/includes/ajax-actions.php	(revision 34105)
+++ src/wp-admin/includes/ajax-actions.php	(working copy)
@@ -1525,7 +1525,7 @@
 function wp_ajax_get_permalink() {
 	check_ajax_referer( 'getpermalink', 'getpermalinknonce' );
 	$post_id = isset($_POST['post_id'])? intval($_POST['post_id']) : 0;
-	wp_die( add_query_arg( array( 'preview' => 'true' ), get_permalink( $post_id ) ) );
+	wp_die( get_preview_post_link( $post_id ) );
 }
 
 /**
Index: src/wp-admin/includes/class-wp-posts-list-table.php
===================================================================
--- src/wp-admin/includes/class-wp-posts-list-table.php	(revision 34105)
+++ src/wp-admin/includes/class-wp-posts-list-table.php	(working copy)
@@ -1157,9 +1157,9 @@
 			$title = _draft_or_post_title();
 			if ( in_array( $post->post_status, array( 'pending', 'draft', 'future' ) ) ) {
 				if ( $can_edit_post ) {
-					$preview_link = set_url_scheme( get_permalink( $post->ID ) );
+					$unpublished_link = set_url_scheme( get_permalink( $post ) );
 					/** This filter is documented in wp-admin/includes/meta-boxes.php */
-					$preview_link = apply_filters( 'preview_post_link', add_query_arg( 'preview', 'true', $preview_link ), $post );
+					$preview_link = get_preview_post_link( $post, array(), $unpublished_link );
 					$actions['view'] = '<a href="' . esc_url( $preview_link ) . '" title="' . esc_attr( sprintf( __( 'Preview &#8220;%s&#8221;' ), $title ) ) . '" rel="permalink">' . __( 'Preview' ) . '</a>';
 				}
 			} elseif ( 'trash' != $post->post_status ) {
Index: src/wp-admin/includes/meta-boxes.php
===================================================================
--- src/wp-admin/includes/meta-boxes.php	(revision 34105)
+++ src/wp-admin/includes/meta-boxes.php	(working copy)
@@ -40,22 +40,10 @@
 <?php if ( is_post_type_viewable( $post_type_object ) ) : ?>
 <div id="preview-action">
 <?php
+$preview_link = esc_url( get_preview_post_link( $post ) );
 if ( 'publish' == $post->post_status ) {
-	$preview_link = esc_url( get_permalink( $post->ID ) );
 	$preview_button = __( 'Preview Changes' );
 } else {
-	$preview_link = set_url_scheme( get_permalink( $post->ID ) );
-
-	/**
-	 * Filter the URI of a post preview in the post submit box.
-	 *
-	 * @since 2.0.5
-	 * @since 4.0.0 $post parameter was added.
-	 *
-	 * @param string  $preview_link URI the user will be directed to for a post preview.
-	 * @param WP_Post $post         Post object.
-	 */
-	$preview_link = esc_url( apply_filters( 'preview_post_link', add_query_arg( 'preview', 'true', $preview_link ), $post ) );
 	$preview_button = __( 'Preview' );
 }
 ?>
Index: src/wp-admin/includes/post.php
===================================================================
--- src/wp-admin/includes/post.php	(revision 34105)
+++ src/wp-admin/includes/post.php	(working copy)
@@ -1318,9 +1318,9 @@
 
 	if ( isset( $view_post ) ) {
 		if ( 'draft' == $post->post_status ) {
-			$preview_link = set_url_scheme( get_permalink( $post->ID ) );
+			$draft_link = set_url_scheme( get_permalink( $post->ID ) );
 			/** This filter is documented in wp-admin/includes/meta-boxes.php */
-			$preview_link = apply_filters( 'preview_post_link', add_query_arg( 'preview', 'true', $preview_link ), $post );
+			$preview_link = get_preview_post_link( $post, array(), $draft_link );
 			$return .= "<span id='view-post-btn'><a href='" . esc_url( $preview_link ) . "' class='button button-small' target='wp-preview-{$post->ID}'>$view_post</a></span>\n";
 		} else {
 			if ( 'publish' === $post->post_status ) {
@@ -1511,20 +1511,17 @@
 	<?php
 
 	if ( $locked ) {
+		$query_args = array();
 		if ( get_post_type_object( $post->post_type )->public ) {
-			$preview_link = set_url_scheme( add_query_arg( 'preview', 'true', get_permalink( $post->ID ) ) );
-
 			if ( 'publish' == $post->post_status || $user->ID != $post->post_author ) {
 				// Latest content is in autosave
 				$nonce = wp_create_nonce( 'post_preview_' . $post->ID );
-				$preview_link = add_query_arg( array( 'preview_id' => $post->ID, 'preview_nonce' => $nonce ), $preview_link );
+				$query_args['preview_id'] = $post->ID;
+				$query_args['preview_nonce'] = $nonce;
 			}
-		} else {
-			$preview_link = '';
 		}
 
-		/** This filter is documented in wp-admin/includes/meta-boxes.php */
-		$preview_link = apply_filters( 'preview_post_link', $preview_link, $post );
+		$preview_link = get_preview_post_link( $post->ID, $query_args );
 
 		/**
 		 * Filter whether to allow the post lock to be overridden.
@@ -1710,7 +1707,7 @@
 	if ( is_wp_error( $saved_post_id ) )
 		wp_die( $saved_post_id->get_error_message() );
 
-	$query_args = array( 'preview' => 'true' );
+	$query_args = array();
 
 	if ( $is_autosave && $saved_post_id ) {
 		$query_args['preview_id'] = $post->ID;
@@ -1720,10 +1717,7 @@
 			$query_args['post_format'] = empty( $_POST['post_format'] ) ? 'standard' : sanitize_key( $_POST['post_format'] );
 	}
 
-	$url = add_query_arg( $query_args, get_permalink( $post->ID ) );
-
-	/** This filter is documented in wp-admin/includes/meta-boxes.php */
-	return apply_filters( 'preview_post_link', $url, $post );
+	return get_preview_post_link( $post, $query_args );
 }
 
 /**
Index: src/wp-includes/admin-bar.php
===================================================================
--- src/wp-includes/admin-bar.php	(revision 34105)
+++ src/wp-includes/admin-bar.php	(working copy)
@@ -532,9 +532,8 @@
 			&& ( $post_type_object->show_in_admin_bar ) )
 		{
 			if ( 'draft' == $post->post_status ) {
-				$preview_link = set_url_scheme( get_permalink( $post->ID ) );
-				/** This filter is documented in wp-admin/includes/meta-boxes.php */
-				$preview_link = apply_filters( 'preview_post_link', add_query_arg( 'preview', 'true', $preview_link ), $post );
+				$draft_link = set_url_scheme( get_permalink( $post->ID ) );
+				$preview_link = get_preview_post_link( $post, array(), $draft_link );
 				$wp_admin_bar->add_menu( array(
 					'id' => 'preview',
 					'title' => $post_type_object->labels->view_item,
Index: src/wp-includes/link-template.php
===================================================================
--- src/wp-includes/link-template.php	(revision 34105)
+++ src/wp-includes/link-template.php	(working copy)
@@ -1159,6 +1159,46 @@
 }
 
 /**
+ * Retrieve preview post link.
+ *
+ * Get the preview post URL. Allow any number of query args to be appended.
+ *
+ * @since 4.4.0
+ *
+ * @param int    $post         Optional. Post ID or WP_Post object. Defaults to global post.
+ * @param array  $query_args   Optional. If preview query arg should be added. Or array of query args to be added.
+ * @param string $preview_link Optional. If a link other than the permalink should be used. Used by _wp_link_page.
+ * @return string
+ */
+function get_preview_post_link( $post = null, $query_args = array(), $preview_link = '' ) {
+	$post = get_post( $post );
+	if ( ! $post ) {
+		return;
+	}
+
+	$post_type_object = get_post_type_object( $post->post_type );
+	if ( is_post_type_viewable( $post_type_object ) ) {
+		if ( ! $preview_link ) {
+			$preview_link = get_permalink( $post );
+		}
+
+		$query_args['preview'] = true;
+		$preview_link = add_query_arg( $query_args, $preview_link );
+	}
+
+	/**
+	 * Filter the URI of a post preview in the post submit box.
+	 *
+	 * @since 2.0.5
+	 * @since 4.4.0 $post parameter was added.
+	 *
+	 * @param string  $preview_link URI the user will be directed to for a post preview.
+	 * @param WP_Post $post         Post object.
+	 */
+	return apply_filters( 'preview_post_link', $preview_link, $post );
+}
+
+/**
  * Retrieve edit posts link for post.
  *
  * Can be used within the WordPress loop or outside of it. Can be used with
Index: src/wp-includes/post-template.php
===================================================================
--- src/wp-includes/post-template.php	(revision 34105)
+++ src/wp-includes/post-template.php	(working copy)
@@ -899,6 +899,7 @@
 function _wp_link_page( $i ) {
 	global $wp_rewrite;
 	$post = get_post();
+	$query_args = array();
 
 	if ( 1 == $i ) {
 		$url = get_permalink();
@@ -912,16 +913,13 @@
 	}
 
 	if ( is_preview() ) {
-		$url = add_query_arg( array(
-			'preview' => 'true'
-		), $url );
 
 		if ( ( 'draft' !== $post->post_status ) && isset( $_GET['preview_id'], $_GET['preview_nonce'] ) ) {
-			$url = add_query_arg( array(
-				'preview_id'    => wp_unslash( $_GET['preview_id'] ),
-				'preview_nonce' => wp_unslash( $_GET['preview_nonce'] )
-			), $url );
+			$query_args['preview_id'] = wp_unslash( $_GET['preview_id'] );
+			$query_args['preview_nonce'] = wp_unslash( $_GET['preview_nonce'] );
 		}
+
+		$url = get_preview_post_link( $post, $query_args, $url );
 	}
 
 	return '<a href="' . esc_url( $url ) . '">';
