PluginProbe
aBlocks – Gutenberg Blocks, User Dashboard Builder, Popup Builder, Form Builder & Animation Builder / 2.11.0
aBlocks – Gutenberg Blocks, User Dashboard Builder, Popup Builder, Form Builder & Animation Builder v2.11.0
2.13.0 2.13.1 2.12.0 2.11.1 2.11.0 2.10.0 2.9.0 2.7.4 2.7.5 2.7.6 2.7.7 2.8.0 2.8.1 2.9.1 trunk 1.0 1.0-beta1 1.0-beta2 1.0-beta3 1.0.1 1.0.2 1.0.3 1.1.0 1.1.1 1.1.2 All 80 releases
ablocks / includes / classes / page-cache / admin-bar.php

admin-bar.php in aBlocks – Gutenberg Blocks, User Dashboard Builder, Popup Builder, Form Builder & Animation Builder 2.11.0, at includes/classes/page-cache/admin-bar.php

346 lines 9.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 namespace ABlocks\Classes\PageCache;
3
4 if ( ! defined( 'ABSPATH' ) ) {
5 exit;
6 }
7
8 use ABlocks\Performance\FragmentCache;
9 use ABlocks\Performance\TemplateCache;
10
11 /**
12 * Page Cache — toolbar controls.
13 *
14 * Cache entries are built lazily, on the first visit to each URL, so the
15 * question an editor actually has while looking at a page is "is *this* page
16 * cached, and can I rebuild it right now?". Answering that from the toolbar is
17 * far more direct than a settings screen, which can only ever talk about the
18 * cache in aggregate.
19 *
20 * The node reports the current page's state and offers, per page, a purge and a
21 * rebuild — plus a purge-everything for the whole site.
22 *
23 * Nothing here can be served stale: logged-out visitors are the only ones who
24 * receive cached HTML, so a logged-in user with a toolbar is by definition
25 * looking at a freshly rendered page.
26 */
27 class AdminBar {
28
29 const ACTION = 'ablocks_cache_action';
30 const NOTICE_TRANSIENT = 'ablocks_cache_notice_';
31
32 public static function init() {
33 add_action( 'admin_bar_menu', [ __CLASS__, 'menu' ], 100 );
34 add_action( 'admin_post_' . self::ACTION, [ __CLASS__, 'handle' ] );
35 }
36
37 /**
38 * Who may operate these controls.
39 *
40 * @return bool
41 */
42 public static function user_can() {
43 $capability = (string) apply_filters( 'ablocks/perf/page_cache/manage_capability', 'manage_options' );
44 return current_user_can( $capability );
45 }
46
47 /**
48 * Build the toolbar node.
49 *
50 * @param \WP_Admin_Bar $bar Toolbar instance.
51 */
52 public static function menu( $bar ) {
53 if ( ! is_admin_bar_showing() || ! self::user_can() ) {
54 return;
55 }
56 if ( ! Rules::is_enabled() ) {
57 return;
58 }
59
60 $url = self::current_url();
61 $cached = $url ? self::is_cached( $url ) : false;
62
63 $bar->add_node(
64 [
65 'id' => 'ablocks-cache',
66 'title' => __( 'aBlocks Cache', 'ablocks' ),
67 'href' => admin_url( 'admin.php?page=ablocks-settings&path=performance&sub=caching' ),
68 'meta' => [ 'title' => __( 'aBlocks Cache', 'ablocks' ) ],
69 ]
70 );
71
72 // The current page's state moves into the submenu rather than the top
73 // label, which stays a stable product name. A toolbar item whose text
74 // changes as you browse is hard to aim at and easy to misread.
75 if ( $url ) {
76 $bar->add_node(
77 [
78 'id' => 'ablocks-cache-state',
79 'parent' => 'ablocks-cache',
80 'title' => self::node_title( $url, $cached ),
81 'meta' => [ 'class' => 'ablocks-cache-state' ],
82 ]
83 );
84 }
85
86 $notice = get_transient( self::NOTICE_TRANSIENT . get_current_user_id() );
87 if ( $notice ) {
88 delete_transient( self::NOTICE_TRANSIENT . get_current_user_id() );
89 $bar->add_node(
90 [
91 'id' => 'ablocks-cache-notice',
92 'parent' => 'ablocks-cache',
93 'title' => esc_html( $notice ),
94 ]
95 );
96 }
97
98 if ( $url ) {
99 // Only offered when there is something to remove, so the menu never
100 // presents an action that would do nothing.
101 if ( $cached ) {
102 $bar->add_node(
103 [
104 'id' => 'ablocks-cache-purge-current',
105 'parent' => 'ablocks-cache',
106 'title' => __( 'Clear cache for this page', 'ablocks' ),
107 'href' => self::action_url( 'purge_current', $url ),
108 ]
109 );
110 }
111
112 $bar->add_node(
113 [
114 'id' => 'ablocks-cache-warm-current',
115 'parent' => 'ablocks-cache',
116 'title' => $cached
117 ? __( 'Rebuild this page', 'ablocks' )
118 : __( 'Cache this page now', 'ablocks' ),
119 'href' => self::action_url( 'warm_current', $url ),
120 ]
121 );
122 }//end if
123
124 $bar->add_node(
125 [
126 'id' => 'ablocks-cache-purge-all',
127 'parent' => 'ablocks-cache',
128 'title' => __( 'Clear entire cache', 'ablocks' ),
129 'href' => self::action_url( 'purge_all' ),
130 'meta' => [
131 'onclick' => "return confirm('" . esc_js( __( 'Clear the cache for the whole site? Pages rebuild as visitors request them.', 'ablocks' ) ) . "');",
132 ],
133 ]
134 );
135
136 $bar->add_node(
137 [
138 'id' => 'ablocks-cache-settings',
139 'parent' => 'ablocks-cache',
140 'title' => __( 'Cache settings', 'ablocks' ),
141 'href' => admin_url( 'admin.php?page=ablocks-settings&path=performance&sub=caching' ),
142 ]
143 );
144 }
145
146 /**
147 * Run the requested action, then return the user where they came from.
148 */
149 public static function handle() {
150 if ( ! self::user_can() ) {
151 wp_die( esc_html__( 'You are not allowed to manage the cache.', 'ablocks' ), 403 );
152 }
153
154 $do = isset( $_GET['do'] ) ? sanitize_key( wp_unslash( $_GET['do'] ) ) : '';
155 check_admin_referer( self::ACTION . '_' . $do );
156
157 $target = isset( $_GET['target'] ) ? esc_url_raw( wp_unslash( $_GET['target'] ) ) : '';
158 $target = self::same_origin( $target ) ? $target : '';
159
160 switch ( $do ) {
161 case 'purge_current':
162 $message = $target && Store::delete_url( $target )
163 ? __( 'Cache cleared for this page.', 'ablocks' )
164 : __( 'This page was not cached.', 'ablocks' );
165 break;
166
167 case 'warm_current':
168 if ( $target ) {
169 // Purge first so the request rebuilds rather than being a
170 // no-op against an entry that already exists.
171 Store::delete_url( $target );
172 Scheduler::warm_url( $target );
173 $message = __( 'Rebuilding this page in the background.', 'ablocks' );
174 } else {
175 $message = __( 'Nothing to rebuild.', 'ablocks' );
176 }
177 break;
178
179 case 'purge_all':
180 $removed = Store::flush();
181 FragmentCache::bump_version();
182 TemplateCache::maybe_bump_version();
183 /* translators: %d: number of files removed. */
184 $message = sprintf( __( 'Cache cleared (%d files removed).', 'ablocks' ), (int) $removed );
185 break;
186
187 default:
188 $message = '';
189 }//end switch
190
191 if ( $message ) {
192 // Carried in a short-lived transient rather than a query argument:
193 // any unrecognised query arg makes the destination uncacheable, so a
194 // redirect that advertised the result would quietly prevent the very
195 // page just rebuilt from being cached again.
196 set_transient( self::NOTICE_TRANSIENT . get_current_user_id(), $message, 60 );
197 }
198
199 wp_safe_redirect( self::return_url( $target ) );
200 exit;
201 }
202
203 /**
204 * Submenu label reporting whether the page being viewed is cached.
205 *
206 * @param string $url Current URL, if any.
207 * @param bool $cached Whether it is cached.
208 * @return string
209 */
210 private static function node_title( $url, $cached ) {
211 if ( ! $url ) {
212 return '';
213 }
214
215 return $cached
216 ? __( 'This page: cached', 'ablocks' )
217 : __( 'This page: not cached', 'ablocks' );
218 }
219
220 /**
221 * Is there a cache entry for a URL?
222 *
223 * @param string $url URL to check.
224 * @return bool
225 */
226 private static function is_cached( $url ) {
227 $parts = wp_parse_url( $url );
228 if ( empty( $parts['host'] ) ) {
229 return false;
230 }
231 $path = isset( $parts['path'] ) ? $parts['path'] : '/';
232 $file = Store::file_path( $parts['host'], $path, Store::current_variant() );
233
234 return $file && file_exists( $file );
235 }
236
237 /**
238 * The page these controls act on.
239 *
240 * On the frontend that is the page being viewed. In the admin there is no
241 * such page, except on a post editor screen, where the post being edited is
242 * unambiguously what the user means.
243 *
244 * @return string Absolute URL, or '' when there is no meaningful target.
245 */
246 private static function current_url() {
247 if ( ! is_admin() ) {
248 if ( is_singular() || is_home() || is_front_page() || is_archive() ) {
249 $path = isset( $_SERVER['REQUEST_URI'] ) ? wp_unslash( $_SERVER['REQUEST_URI'] ) : '/';
250 $path = is_string( $path ) ? (string) strtok( $path, '?' ) : '/';
251
252 // Built from the host rather than home_url( $path ), because
253 // REQUEST_URI already contains any subdirectory the site is
254 // installed in — passing it to home_url() would repeat that
255 // segment and produce /blog/blog/page/ on a subdirectory install.
256 $host = wp_parse_url( home_url(), PHP_URL_HOST );
257 if ( ! $host ) {
258 return '';
259 }
260 $port = wp_parse_url( home_url(), PHP_URL_PORT );
261
262 return ( is_ssl() ? 'https://' : 'http://' ) . $host . ( $port ? ':' . $port : '' ) . $path;
263 }
264 return '';
265 }
266
267 $screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null;
268 if ( $screen && 'post' === $screen->base ) {
269 // Reading which post the editor screen is showing, to label the menu.
270 // Nothing is written, and the actions themselves are nonce-checked in
271 // handle().
272 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Screen context only; no state is changed here.
273 $post_id = isset( $_GET['post'] ) ? absint( wp_unslash( $_GET['post'] ) ) : 0;
274 if ( $post_id && 'publish' === get_post_status( $post_id ) ) {
275 $permalink = get_permalink( $post_id );
276 return $permalink ? $permalink : '';
277 }
278 }
279
280 return '';
281 }
282
283 /**
284 * Build a nonced action URL.
285 *
286 * @param string $do Action key.
287 * @param string $target Optional URL the action applies to.
288 * @return string
289 */
290 private static function action_url( $do, $target = '' ) {
291 $args = [
292 'action' => self::ACTION,
293 'do' => $do,
294 ];
295 if ( $target ) {
296 $args['target'] = rawurlencode( $target );
297 }
298
299 return wp_nonce_url(
300 add_query_arg( $args, admin_url( 'admin-post.php' ) ),
301 self::ACTION . '_' . $do
302 );
303 }
304
305 /**
306 * Where to send the user afterwards.
307 *
308 * @param string $target Action target.
309 * @return string
310 */
311 private static function return_url( $target ) {
312 $referer = wp_get_referer();
313 if ( $referer && self::same_origin( $referer ) ) {
314 return $referer;
315 }
316 if ( $target ) {
317 return $target;
318 }
319 return admin_url();
320 }
321
322 /**
323 * Does a URL belong to this site?
324 *
325 * Both the redirect target and the action target come from the request, so
326 * neither is trusted: one could send a user off-site, the other could make
327 * the server fetch an arbitrary host.
328 *
329 * @param string $url Candidate URL.
330 * @return bool
331 */
332 private static function same_origin( $url ) {
333 if ( empty( $url ) ) {
334 return false;
335 }
336 $home = wp_parse_url( home_url() );
337 $want = wp_parse_url( $url );
338
339 if ( empty( $want['host'] ) || empty( $home['host'] ) ) {
340 return false;
341 }
342
343 return strtolower( $want['host'] ) === strtolower( $home['host'] );
344 }
345 }
346