Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

VIP Cache Manager: implement "Purge page and its assets" admin bar button #1880

Merged
merged 14 commits into from
Dec 15, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,4 @@
"eslint": "5.16.0",
"husky": "4.2.3"
}
}
}
73 changes: 73 additions & 0 deletions vip-cache-manager/js/admin-bar.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
( async () => {
async function postData( url = '', data = {} ) {
// Default options are marked with *
const response = await fetch( url, {
method: 'POST',
credentials: 'same-origin',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify( { ...data } )
} );
return response.json();
}

// Indicates whether there's a purge request happening
let purgeInProgress = false;
// Stores the ref to the DOM node
let btn;

/**
* Grab all the necesary URLs (incl. scripts and CSS) for the purge.
*/
const getURLsToPurge = () => {
return [document.location.toString()].concat(
Array.from( document.querySelectorAll( 'script[src]' ) ).map( ( { src } ) => src ),
Array.from( document.querySelectorAll( 'link[rel=stylesheet]' ) ).map( ( { href } ) => href )
)
.filter( url => url.includes( document.location.hostname ) );
}

/**
* Cache purge click handler.
*
* @param {Event} e
*/
async function onClickHandler( e ) {
e.preventDefault();

if ( purgeInProgress ) {
return;
}

let { nonce = '', ajaxurl = '' } = window.VIPPageFlush || {};

if ( !( nonce && ajaxurl ) ) {
alert( 'VIP Cache Manager: page cache purging disabled' );
}

purgeInProgress = true;

const urls = getURLsToPurge();

try {
const res = await postData( ajaxurl, { nonce, urls } );
const { success, data } = res;

btn.textContent = data.result || 'Success';
btn.disabled = true;
btn.removeEventListener( 'click', onClickHandler );
} catch ( err ) {
purgeInProgress = false;
btn.textContent = '❌ Cache Purge Failed';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there the option to use https://make.wordpress.org/accessibility/handbook/markup/wp-a11y-speak/ here, so that screen reader users get notified on the success / failure of the action?

}
}

document.addEventListener( 'DOMContentLoaded', () => {
btn = document.querySelector( '#wp-admin-bar-vip-purge-page > .ab-item' )
if ( btn ) {
btn.addEventListener( 'click', onClickHandler );
}
} );

} )();
110 changes: 107 additions & 3 deletions vip-cache-manager/vip-cache-manager.php
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ public function init() {
add_action( 'activity_box_end', array( $this, 'get_manual_purge_link' ), 100 );

add_action( 'shutdown', array( $this, 'execute_purges' ) );
add_action( 'admin_bar_menu', [ $this, 'admin_bar_callback' ], 100, 1 );
add_action( 'wp_enqueue_scripts', [ $this, 'button_enqueue_scripts' ] );
add_action( 'wp_ajax_vip_purge_page_cache', [ $this, 'ajax_vip_purge_page_cache' ] );
}

public function get_queued_purge_urls() {
Expand All @@ -61,6 +64,108 @@ public function clear_queued_purge_urls() {
$this->purge_urls = [];
}

/**
* Display a button to purge the cache for the specific URL and its assets
*
* @return void
*/
public function admin_bar_callback( WP_Admin_Bar $admin_bar ) {
if ( is_admin() || ! current_user_can( 'manage_options' ) ) {
return;
}

$admin_bar->add_menu(
[
'id' => 'vip-purge-page',
'parent' => null,
'group' => null,
'title' => 'Flush Cache for Page',
'href' => '#',
rinatkhaziev marked this conversation as resolved.
Show resolved Hide resolved
'meta' => [
'title' => 'Flush Page cache for this page and its assets',
],
]
);
}

/**
* Enqueue the button for users who have the needed caps.
*
* @return void
*/
public function button_enqueue_scripts() {
if ( ! current_user_can( 'manage_options' ) ) {
return;
}

wp_enqueue_script( 'purge-page-cache-btn', plugins_url( '/js/admin-bar.js', __FILE__ ), [], '1.1', true );
wp_localize_script( 'purge-page-cache-btn', 'VIPPageFlush', [
'nonce' => wp_create_nonce( 'purge-page' ),
'ajaxurl' => add_query_arg( [ 'action' => 'vip_purge_page_cache' ], admin_url( 'admin-ajax.php' ),
),
] );
}

/**
* AJAX callback that performs basic security checks and payload validation and queues urls for the purge.
*
* @return void
*/
public function ajax_vip_purge_page_cache() {
$req = json_decode( file_get_contents( 'php://input' ) );

if ( json_last_error() ) {
\Automattic\VIP\Stats\send_pixel( [
'vip-go-cache-manager-url-purge-status' => 'bad-payload',
] );
wp_send_json_error( [ 'error' => 'Malformed payload' ], 400 );
}

if ( ! current_user_can( 'manage_options' ) ) {
\Automattic\VIP\Stats\send_pixel( [
'vip-go-cache-manager-url-purge-status' => 'deny-permissions',
] );

wp_send_json_error( [ 'error' => 'Unauthorized' ], 403 );
}

if ( ! ( isset( $req->nonce ) && wp_verify_nonce( $req->nonce, 'purge-page' ) ) ) {
\Automattic\VIP\Stats\send_pixel( [
'vip-go-cache-manager-url-purge-status' => 'deny-nonce',
] );

wp_send_json_error( [ 'error' => 'Unauthorized' ], 403 );
}

$urls = is_array( $req->urls ) && ! empty( $req->urls ) ? $req->urls : [];

if ( empty( $urls ) ) {
\Automattic\VIP\Stats\send_pixel( [
'vip-go-cache-manager-url-purge-status' => 'deny-no-urls',
] );

wp_send_json_error( [ 'error' => 'No URLs' ], 400 );
}

// URLs are validated in queue_purge_url.
foreach ( $urls as $url_to_purge ) {
$this->queue_purge_url( $url_to_purge );
}

\Automattic\VIP\Stats\send_pixel( [
'vip-go-cache-manager-action' => 'user-url-purge',
'vip-go-cache-manager-url-purge-by-site' => VIP_GO_APP_ID,
'vip-go-cache-manager-url-purge-status' => 'success',
] );

// Optimistically tell that the operation is successful and bail.
wp_send_json_success(
[
'result' => sprintf( '✅ %d URLS purged', count( $urls ) ),
]
);
}

public function get_manual_purge_link() {
if ( ! $this->can_purge_cache() ) {
return;
Expand Down Expand Up @@ -333,9 +438,8 @@ public function queue_post_purge( $post_id ) {

$post = get_post( $post_id );
if ( empty( $post ) ||
'revision' === $post->post_type ||
! in_array( get_post_status( $post_id ), array( 'publish', 'inherit', 'trash' ), true ) )
{
'revision' === $post->post_type ||
! in_array( get_post_status( $post_id ), array( 'publish', 'inherit', 'trash' ), true ) ) {
return false;
}

Expand Down