Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
| Total | |
94.62% |
88 / 93 |
|
100.00% |
6 / 6 |
CRAP | n/a |
0 / 0 |
|
| wpcom_write_print_post_publish_url_cleanup | |
100.00% |
8 / 8 |
|
100.00% |
1 / 1 |
3 | |||
| wpcom_write_should_show_post_publish_checklist | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
4 | |||
| wpcom_write_post_publish_launch_url | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
| wpcom_write_get_post_publish_checklist_strings | |
100.00% |
19 / 19 |
|
100.00% |
1 / 1 |
1 | |||
| wpcom_write_enqueue_post_publish_checklist_assets | |
100.00% |
37 / 37 |
|
100.00% |
1 / 1 |
3 | |||
| wpcom_write_render_post_publish_checklist | |
100.00% |
15 / 15 |
|
100.00% |
1 / 1 |
2 | |||
| 1 | <?php |
| 2 | /** |
| 3 | * Write — post-publish next-steps checklist. |
| 4 | * |
| 5 | * After a post is published in the Write editor, the editor redirects the author |
| 6 | * to the published post with a `wpcom_write_published` marker (see view.js). On a |
| 7 | * Coming Soon (private-by-default) site that post is not yet public, so we overlay |
| 8 | * a short next-steps checklist — Launch your site, Share your post — onto the post. |
| 9 | * This keeps "Publish" honest (it didn't make the post public) and guides the |
| 10 | * author toward launching and sharing. |
| 11 | * |
| 12 | * The overlay only renders on the immediate post-publish navigation (the marker is |
| 13 | * present), for a viewer who can actually launch the site, while the site is still |
| 14 | * Coming Soon. Public sites, ordinary visitors, and later visits to the post are |
| 15 | * unaffected. |
| 16 | * |
| 17 | * @package automattic/jetpack-mu-wpcom |
| 18 | */ |
| 19 | |
| 20 | if ( ! defined( 'ABSPATH' ) ) { |
| 21 | exit; |
| 22 | } |
| 23 | |
| 24 | /** |
| 25 | * Query-arg marker the Write editor appends to the published-post URL on publish. |
| 26 | */ |
| 27 | const WPCOM_WRITE_PUBLISHED_MARKER = 'wpcom_write_published'; |
| 28 | |
| 29 | /** |
| 30 | * Strip the post-publish query args from the author's address bar. |
| 31 | * |
| 32 | * The redirect tags the permalink so the post-publish surfaces know a publish |
| 33 | * just happened, but the author is left looking at their own live post with our |
| 34 | * bookkeeping attached — and would share that URL if they copied it. Cleaning up |
| 35 | * belongs here rather than in either card: both are conditional, and the writer |
| 36 | * who has already answered the survey sees no card at all, so nothing would run. |
| 37 | * |
| 38 | * Server-side state has already been read by the time this executes, so removing |
| 39 | * the args client-side changes nothing but the visible URL. |
| 40 | * |
| 41 | * @return void |
| 42 | */ |
| 43 | function wpcom_write_print_post_publish_url_cleanup() { |
| 44 | // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Display-only marker; writes nothing. |
| 45 | if ( ! isset( $_GET[ WPCOM_WRITE_PUBLISHED_MARKER ] ) || ! is_singular( 'post' ) ) { |
| 46 | return; |
| 47 | } |
| 48 | |
| 49 | // `source` is only stripped alongside the marker, so a campaign link that |
| 50 | // carries its own `source` to a post is left alone. |
| 51 | // Hex-escaped so the encoded value can never break out of the inline script tag. |
| 52 | $args = wp_json_encode( array( WPCOM_WRITE_PUBLISHED_MARKER, 'source' ), JSON_HEX_TAG | JSON_HEX_AMP ); |
| 53 | |
| 54 | wp_print_inline_script_tag( |
| 55 | '( function () { |
| 56 | try { |
| 57 | var url = new URL( window.location.href ); |
| 58 | ' . $args . '.forEach( function ( arg ) { url.searchParams.delete( arg ); } ); |
| 59 | window.history.replaceState( null, "", url.href ); |
| 60 | } catch ( e ) {} |
| 61 | } )();' |
| 62 | ); |
| 63 | } |
| 64 | add_action( 'wp_footer', 'wpcom_write_print_post_publish_url_cleanup', 5 ); |
| 65 | |
| 66 | /** |
| 67 | * Whether the post-publish checklist overlay should render on the current request. |
| 68 | * |
| 69 | * All of the following must hold: |
| 70 | * - the request carries the Write editor's post-publish marker; |
| 71 | * - we're on a single post's front-end view; |
| 72 | * - the viewer can launch the site (`manage_options`) — which also means they |
| 73 | * bypass the Coming Soon splash and therefore see the post we overlay onto; |
| 74 | * - the site is still Coming Soon, so the post isn't publicly visible yet. |
| 75 | * |
| 76 | * @return bool True when the overlay should be shown. |
| 77 | */ |
| 78 | function wpcom_write_should_show_post_publish_checklist() { |
| 79 | // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Display-only marker; writes nothing, mirrors the coming-soon share-code read. |
| 80 | if ( ! isset( $_GET[ WPCOM_WRITE_PUBLISHED_MARKER ] ) ) { |
| 81 | return false; |
| 82 | } |
| 83 | |
| 84 | if ( ! is_singular( 'post' ) ) { |
| 85 | return false; |
| 86 | } |
| 87 | |
| 88 | // Only the author/manager who can launch the site — anyone who can't launch |
| 89 | // also can't act on the checklist, and would see the Coming Soon splash here. |
| 90 | if ( ! current_user_can( 'manage_options' ) ) { |
| 91 | return false; |
| 92 | } |
| 93 | |
| 94 | // Only while the site is private by default; a launched site needs no nudge. |
| 95 | return 1 === (int) get_option( 'wpcom_public_coming_soon' ); |
| 96 | } |
| 97 | |
| 98 | /** |
| 99 | * URL the "Launch your site" task links to. |
| 100 | * |
| 101 | * Points at Calypso's canonical launch-site flow, which takes the user through |
| 102 | * domain/plan (skipped if already chosen) and flips a Coming Soon site public. |
| 103 | * Unlike the launchpad, it does not depend on the site having |
| 104 | * launchpad_screen='full', so it works for these sites as created today. |
| 105 | * |
| 106 | * @return string The launch URL for this site. |
| 107 | */ |
| 108 | function wpcom_write_post_publish_launch_url() { |
| 109 | $host = wp_parse_url( home_url(), PHP_URL_HOST ); |
| 110 | |
| 111 | return 'https://wordpress.com/start/launch-site?siteSlug=' . rawurlencode( (string) $host ); |
| 112 | } |
| 113 | |
| 114 | /** |
| 115 | * Translated strings for the post-publish checklist overlay. |
| 116 | * |
| 117 | * @return array<string, string> Map of key -> translated string. |
| 118 | */ |
| 119 | function wpcom_write_get_post_publish_checklist_strings() { |
| 120 | return array( |
| 121 | 'heading' => __( 'Your post is saved!', 'jetpack-mu-wpcom' ), |
| 122 | 'description' => __( 'Your site is still private, so only you can see this post. Launch your site to share it with the world.', 'jetpack-mu-wpcom' ), |
| 123 | 'launchTitle' => __( 'Launch your site', 'jetpack-mu-wpcom' ), |
| 124 | 'launchDesc' => __( 'Make your site public.', 'jetpack-mu-wpcom' ), |
| 125 | 'shareTitle' => __( 'Share your post', 'jetpack-mu-wpcom' ), |
| 126 | 'shareDesc' => __( 'Available after you launch.', 'jetpack-mu-wpcom' ), |
| 127 | 'launchCta' => __( 'Launch your site', 'jetpack-mu-wpcom' ), |
| 128 | 'dismiss' => __( 'Maybe later', 'jetpack-mu-wpcom' ), |
| 129 | 'close' => __( 'Close', 'jetpack-mu-wpcom' ), |
| 130 | // Inline email-verification step, swapped in when the author tries to |
| 131 | // launch with an unverified email (see post-publish-checklist.js). |
| 132 | 'verifyTitle' => __( 'Confirm your email to launch', 'jetpack-mu-wpcom' ), |
| 133 | 'verifyDesc' => __( 'Your site can go public once your email is confirmed. Check your inbox for the confirmation link.', 'jetpack-mu-wpcom' ), |
| 134 | 'confirmCta' => __( "I've confirmed — launch", 'jetpack-mu-wpcom' ), |
| 135 | 'resendCta' => __( 'Resend verification email', 'jetpack-mu-wpcom' ), |
| 136 | 'resendSent' => __( 'Verification email sent. Check your inbox.', 'jetpack-mu-wpcom' ), |
| 137 | 'resendError' => __( 'Something went wrong. Please try again.', 'jetpack-mu-wpcom' ), |
| 138 | 'checking' => __( 'Checking…', 'jetpack-mu-wpcom' ), |
| 139 | 'stillUnverified' => __( "We couldn't confirm your email yet. Click the link in the email, then try again.", 'jetpack-mu-wpcom' ), |
| 140 | ); |
| 141 | } |
| 142 | |
| 143 | /** |
| 144 | * Enqueue the overlay assets when the checklist should render. |
| 145 | * |
| 146 | * @return void |
| 147 | */ |
| 148 | function wpcom_write_enqueue_post_publish_checklist_assets() { |
| 149 | if ( ! wpcom_write_should_show_post_publish_checklist() ) { |
| 150 | return; |
| 151 | } |
| 152 | |
| 153 | wp_enqueue_style( |
| 154 | 'wpcom-write-post-publish-checklist', |
| 155 | wpcom_write_asset_url( 'post-publish-checklist.css' ), |
| 156 | array(), |
| 157 | WPCOM_WRITE_VERSION |
| 158 | ); |
| 159 | |
| 160 | wp_enqueue_script( |
| 161 | 'wpcom-write-post-publish-checklist', |
| 162 | wpcom_write_asset_url( 'post-publish-checklist.js' ), |
| 163 | array(), |
| 164 | WPCOM_WRITE_VERSION, |
| 165 | true |
| 166 | ); |
| 167 | |
| 168 | $strings = wpcom_write_get_post_publish_checklist_strings(); |
| 169 | |
| 170 | wp_localize_script( |
| 171 | 'wpcom-write-post-publish-checklist', |
| 172 | 'wpcomWritePostPublishChecklist', |
| 173 | array( |
| 174 | 'launchUrl' => wpcom_write_post_publish_launch_url(), |
| 175 | 'marker' => WPCOM_WRITE_PUBLISHED_MARKER, |
| 176 | // Computed server-side: a Simple site serves no REST at its own host, so |
| 177 | // the overlay can't fetch this — it reads the value rendered into the page. |
| 178 | // Sent as a '1'/'0' string because wp_localize_script() stringifies scalars |
| 179 | // (bool true -> '1', false -> ''); the JS compares against '1'. |
| 180 | 'blocked' => wpcom_write_launch_blocked_for_unverified_email() ? '1' : '0', |
| 181 | // admin-ajax transport for the inline resend / re-check (a Simple site |
| 182 | // serves no REST at its own host); nonce guards both actions. |
| 183 | 'ajaxUrl' => admin_url( 'admin-ajax.php' ), |
| 184 | 'nonce' => wp_create_nonce( WPCOM_WRITE_EMAIL_VERIFICATION_NONCE ), |
| 185 | 'strings' => array( |
| 186 | 'verifyTitle' => $strings['verifyTitle'], |
| 187 | 'verifyDesc' => $strings['verifyDesc'], |
| 188 | 'confirmCta' => $strings['confirmCta'], |
| 189 | 'resendCta' => $strings['resendCta'], |
| 190 | 'resendSent' => $strings['resendSent'], |
| 191 | 'resendError' => $strings['resendError'], |
| 192 | 'checking' => $strings['checking'], |
| 193 | 'stillUnverified' => $strings['stillUnverified'], |
| 194 | ), |
| 195 | ) |
| 196 | ); |
| 197 | } |
| 198 | add_action( 'wp_enqueue_scripts', 'wpcom_write_enqueue_post_publish_checklist_assets' ); |
| 199 | |
| 200 | /** |
| 201 | * Render the post-publish checklist overlay markup in the footer. |
| 202 | */ |
| 203 | add_action( 'wp_footer', 'wpcom_write_render_post_publish_checklist' ); |
| 204 | |
| 205 | /** |
| 206 | * Output the overlay markup. |
| 207 | * |
| 208 | * Plain markup wired up by post-publish-checklist.js — no Interactivity API, since |
| 209 | * this renders on an arbitrary theme front-end, not the Write editor surface. |
| 210 | * |
| 211 | * @return void |
| 212 | */ |
| 213 | function wpcom_write_render_post_publish_checklist() { |
| 214 | if ( ! wpcom_write_should_show_post_publish_checklist() ) { |
| 215 | return; |
| 216 | } |
| 217 | |
| 218 | $strings = wpcom_write_get_post_publish_checklist_strings(); |
| 219 | ?> |
| 220 | <div class="wpcom-write-ppc" role="dialog" aria-modal="true" aria-labelledby="wpcom-write-ppc-title" hidden> |
| 221 | <div class="wpcom-write-ppc__backdrop" data-wpcom-write-ppc-dismiss></div> |
| 222 | <div class="wpcom-write-ppc__card"> |
| 223 | <button type="button" class="wpcom-write-ppc__close" data-wpcom-write-ppc-dismiss aria-label="<?php echo esc_attr( $strings['close'] ); ?>">×</button> |
| 224 | <div class="wpcom-write-ppc__badge" aria-hidden="true">✓</div> |
| 225 | <h2 id="wpcom-write-ppc-title" class="wpcom-write-ppc__title"><?php echo esc_html( $strings['heading'] ); ?></h2> |
| 226 | <p class="wpcom-write-ppc__desc"><?php echo esc_html( $strings['description'] ); ?></p> |
| 227 | <ul class="wpcom-write-ppc__list"> |
| 228 | <li class="wpcom-write-ppc__item"> |
| 229 | <span class="wpcom-write-ppc__bullet" aria-hidden="true"></span> |
| 230 | <span class="wpcom-write-ppc__item-text"> |
| 231 | <span class="wpcom-write-ppc__item-title"><?php echo esc_html( $strings['launchTitle'] ); ?></span> |
| 232 | <span class="wpcom-write-ppc__item-desc"><?php echo esc_html( $strings['launchDesc'] ); ?></span> |
| 233 | </span> |
| 234 | </li> |
| 235 | <li class="wpcom-write-ppc__item is-disabled" aria-disabled="true"> |
| 236 | <span class="wpcom-write-ppc__bullet" aria-hidden="true"></span> |
| 237 | <span class="wpcom-write-ppc__item-text"> |
| 238 | <span class="wpcom-write-ppc__item-title"><?php echo esc_html( $strings['shareTitle'] ); ?></span> |
| 239 | <span class="wpcom-write-ppc__item-desc"><?php echo esc_html( $strings['shareDesc'] ); ?></span> |
| 240 | </span> |
| 241 | </li> |
| 242 | </ul> |
| 243 | <button type="button" class="wpcom-write-ppc__launch" data-wpcom-write-ppc-launch><?php echo esc_html( $strings['launchCta'] ); ?></button> |
| 244 | <button type="button" class="wpcom-write-ppc__dismiss" data-wpcom-write-ppc-dismiss><?php echo esc_html( $strings['dismiss'] ); ?></button> |
| 245 | </div> |
| 246 | </div> |
| 247 | <?php |
| 248 | } |