| @@ -45,12 +45,18 @@ | ||
| 45 | 45 | 'id' => 'cache_disabled', |
| 46 | 46 | 'priority' => 5, |
| 47 | 47 | 'title' => __( 'Enable page caching', 'xspeed' ), |
| 48 | 48 | 'detail' => __( 'Caching is off, so every visit renders the full page. Turning it on is the single biggest speed win.', 'xspeed' ), |
| 49 | - 'action' => array( | |
| 50 | - 'type' => 'link', | |
| 51 | - 'hash' => '#cache', | |
| 52 | - 'label' => __( 'Go to Cache settings', 'xspeed' ), | |
| 49 | + 'action' => array_merge( | |
| 50 | + array( 'type' => 'link' ), | |
| 51 | + // Lands ON the enable toggle, not merely on the panel | |
| 52 | + // that contains it (issue #49). | |
| 53 | + // No `suggest` here: cache_enabled is deliberately outside | |
| 54 | + // the cache schema (it drives the drop-in install), so the | |
| 55 | + // Apply strip — which only renders on a schema row — could | |
| 56 | + // never appear for it. Offering a value nothing can apply | |
| 57 | + // is worse than offering none. | |
| 58 | + Deep_Link::action( __( 'Go to Cache settings', 'xspeed' ), 'cache', 'cache_enabled' ) | |
| 53 | 59 | ), |
| 54 | 60 | ); |
| 55 | 61 | // Later rules assume a running cache; report just this one. |
| 56 | 62 | return $out; |
| @@ -100,12 +106,13 @@ | ||
| 100 | 106 | __( '%1$s sets the "%2$s" cookie on anonymous pages, which makes CDNs skip their edge cache for all HTML.', 'xspeed' ), |
| 101 | 107 | $culprit, |
| 102 | 108 | (string) ( $first['name'] ?? '' ) |
| 103 | 109 | ), |
| 104 | - 'action' => array( | |
| 105 | - 'type' => 'link', | |
| 106 | - 'hash' => '#health', | |
| 107 | - 'label' => __( 'See details in Health', 'xspeed' ), | |
| 110 | + 'action' => array_merge( | |
| 111 | + array( 'type' => 'link' ), | |
| 112 | + // Health is a read-only panel — there is no control to | |
| 113 | + // focus, so this one carries the destination only. | |
| 114 | + Deep_Link::action( __( 'See details in Health', 'xspeed' ), 'health' ) | |
| 108 | 115 | ), |
| 109 | 116 | ); |
| 110 | 117 | } |
| 111 | 118 | |
| @@ -115,12 +122,11 @@ | ||
| 115 | 122 | 'id' => 'nginx_snippet_missing', |
| 116 | 123 | 'priority' => 20, |
| 117 | 124 | 'title' => __( 'Apply the nginx server snippet', 'xspeed' ), |
| 118 | 125 | 'detail' => __( 'nginx can serve cache hits directly (~5-15ms TTFB, PHP bypassed) once the snippet is in your server block.', 'xspeed' ), |
| 119 | - 'action' => array( | |
| 120 | - 'type' => 'link', | |
| 121 | - 'hash' => '#cache', | |
| 122 | - 'label' => __( 'Get the snippet', 'xspeed' ), | |
| 126 | + 'action' => array_merge( | |
| 127 | + array( 'type' => 'link' ), | |
| 128 | + Deep_Link::action( __( 'Get the snippet', 'xspeed' ), 'cache', 'nginx_snippet' ) | |
| 123 | 129 | ), |
| 124 | 130 | ); |
| 125 | 131 | } |
| 126 | 132 | |
| @@ -150,12 +156,11 @@ | ||
| 150 | 156 | 'id' => 'object_cache_degraded', |
| 151 | 157 | 'priority' => 30, |
| 152 | 158 | 'title' => __( 'Object cache is configured but not persisting', 'xspeed' ), |
| 153 | 159 | 'detail' => __( 'The backend is not connected, so every request falls back to the database. Run the connection test to see why.', 'xspeed' ), |
| 154 | - 'action' => array( | |
| 155 | - 'type' => 'link', | |
| 156 | - 'hash' => '#object-cache', | |
| 157 | - 'label' => __( 'Test the connection', 'xspeed' ), | |
| 160 | + 'action' => array_merge( | |
| 161 | + array( 'type' => 'link' ), | |
| 162 | + Deep_Link::action( __( 'Test the connection', 'xspeed' ), 'object-cache', 'connection_test' ) | |
| 158 | 163 | ), |
| 159 | 164 | ); |
| 160 | 165 | } |
| 161 | 166 | |
| @@ -166,12 +171,11 @@ | ||
| 166 | 171 | 'id' => 'cloudflare_unverified', |
| 167 | 172 | 'priority' => 35, |
| 168 | 173 | 'title' => __( 'Finish connecting Cloudflare', 'xspeed' ), |
| 169 | 174 | 'detail' => __( 'The Cloudflare module is on but has no verified credentials or zone, so edge purges cannot work.', 'xspeed' ), |
| 170 | - 'action' => array( | |
| 171 | - 'type' => 'link', | |
| 172 | - 'hash' => '#cloudflare', | |
| 173 | - 'label' => __( 'Open Cloudflare settings', 'xspeed' ), | |
| 175 | + 'action' => array_merge( | |
| 176 | + array( 'type' => 'link' ), | |
| 177 | + Deep_Link::action( __( 'Open Cloudflare settings', 'xspeed' ), 'cloudflare', 'api_token' ) | |
| 174 | 178 | ), |
| 175 | 179 | ); |
| 176 | 180 | } |
| 177 | 181 | |
| @@ -253,8 +257,29 @@ | ||
| 253 | 257 | 'applied' => $id, |
| 254 | 258 | 'recommendations' => self::all(), |
| 255 | 259 | ); |
| 256 | 260 | } |
| 261 | + /** | |
| 262 | + * Last chance to resolve a recommendation id this engine doesn't own. | |
| 263 | + * | |
| 264 | + * Free and Pro each ship a recommendation engine with its OWN id | |
| 265 | + * namespace — Free uses underscores (`nginx_snippet_missing`), Pro | |
| 266 | + * uses hyphens (`cache-disabled`) — but only Free's ids reached this | |
| 267 | + * method, so EVERY Pro Apply button returned 404 and the failure was | |
| 268 | + * swallowed by the UI. This seam lets Pro claim its own ids rather | |
| 269 | + * than duplicating the endpoint. (#198) | |
| 270 | + * | |
| 271 | + * Return an array (the same shape this method returns on success) or | |
| 272 | + * a WP_Error to claim the id; return null to decline it. | |
| 273 | + * | |
| 274 | + * @param array|\WP_Error|null $handled Result from a previous filter, or null. | |
| 275 | + * @param string $id The recommendation id being applied. | |
| 276 | + */ | |
| 277 | + $handled = apply_filters( 'xspeed_apply_recommendation', null, $id ); | |
| 278 | + if ( is_array( $handled ) || is_wp_error( $handled ) ) { | |
| 279 | + return $handled; | |
| 280 | + } | |
| 281 | + | |
| 257 | 282 | return new \WP_Error( |
| 258 | 283 | 'xspeed_rec_unknown', |
| 259 | 284 | __( 'Unknown or no-longer-applicable recommendation.', 'xspeed' ), |
| 260 | 285 | array( 'status' => 404 ) |