PluginProbe
Code Snippets / trunk
Code Snippets vtrunk
4.0.0-beta.2 3.10.2 3.10.1 3.10.0 3.10.0-beta.2 3.10.0-beta.1 4.0.0-beta.1 3.9.6 trunk 2.10.0 2.10.1 2.12.0 2.12.1 2.13.0 2.13.1 2.13.2 2.13.3 2.14.0 2.14.1 2.14.2 2.14.3 2.14.4 2.14.5 2.14.6 3.0.0 All 65 releases
← All changes | php/Admin/Menus/Manage/Manage_Menu.php +92 -4 3.10.2 → trunk View file →
@@ -4,8 +4,10 @@
4 4
5 5 use Code_Snippets\Admin\Contextual_Help;
6 6 use Code_Snippets\Admin\Menus\Admin_Menu;
7 7 use Code_Snippets\Controller\Cloud_Search_Controller;
8 +use Code_Snippets\Integration\Evaluate_Functions;
9 +use Code_Snippets\REST_API\Preferences\Demos_Seen_REST_Controller;
8 10 use function Code_Snippets\activate_snippet;
9 11 use function Code_Snippets\code_snippets;
10 12 use function Code_Snippets\get_snippet;
11 13 use function Code_Snippets\Settings\get_setting;
@@ -47,8 +49,9 @@
47 49 $this->screen_options = new Manage_Menu_Screen_Options();
48 50 new Manage_Menu_Bulk_Download();
49 51
50 52 add_action( 'admin_menu', array( $this, 'register_upgrade_menu' ), 500 );
53 + add_filter( 'heartbeat_received', [ $this, 'refresh_run_once_nonce' ] );
51 54 add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_menu_css' ) );
52 55 add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_menu_css' ] );
53 56 }
54 57
@@ -160,8 +163,9 @@
160 163 $classmap = [
161 164 'snippets' => 'manage',
162 165 'add-snippet' => 'edit',
163 166 'edit-snippet' => 'edit',
167 + 'code-snippets-insights' => 'insights',
164 168 'import-code-snippets' => 'import',
165 169 'snippets-settings' => 'settings',
166 170 ];
167 171 $menus = code_snippets()->admin->menus;
@@ -179,18 +183,75 @@
179 183 add_action( 'load-' . $hook, [ $class, 'load' ] );
180 184 }
181 185
182 186 /**
187 + * Query parameter that clears the record of which demos have been watched.
188 + */
189 + public const DEMO_RESET_PARAM = 'demo-reset';
190 +
191 + /**
192 + * Nonce action guarding the watched-demo reset.
193 + */
194 + public const DEMO_RESET_NONCE = 'code_snippets_demo_reset';
195 +
196 + /**
197 + * Build the address that puts the walkthrough tabs back to their "New" state.
198 + *
199 + * @return string
200 + */
201 + public static function get_demo_reset_url(): string {
202 + // Built raw rather than with wp_nonce_url(), which escapes the separator
203 + // for markup: this address is meant to be pasted into the address bar.
204 + return add_query_arg(
205 + [
206 + self::DEMO_RESET_PARAM => '1',
207 + '_wpnonce' => wp_create_nonce( self::DEMO_RESET_NONCE ),
208 + ],
209 + code_snippets()->get_menu_url()
210 + );
211 + }
212 +
213 + /**
214 + * Clear the watched-demo record when asked through the query string.
215 + *
216 + * This is deliberately not exposed in the settings screen: it exists to put
217 + * the walkthrough tabs back to their "New" state for a screenshot or a
218 + * walkthrough of the walkthroughs. Build the address with
219 + * {@see self::get_demo_reset_url()}, which signs it.
220 + *
221 + * @return void
222 + */
223 + private function maybe_reset_demos(): void {
224 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Verified immediately below.
225 + if ( ! isset( $_GET[ self::DEMO_RESET_PARAM ] ) ) {
226 + return;
227 + }
228 +
229 + $nonce = isset( $_GET['_wpnonce'] ) ? sanitize_text_field( wp_unslash( $_GET['_wpnonce'] ) ) : '';
230 +
231 + if ( ! wp_verify_nonce( $nonce, self::DEMO_RESET_NONCE ) || ! code_snippets()->current_user_can() ) {
232 + return;
233 + }
234 +
235 + Demos_Seen_REST_Controller::reset_demos_seen();
236 +
237 + // Redirect so a refresh does not repeat the reset, and the address bar
238 + // is left clean.
239 + wp_safe_redirect( remove_query_arg( [ self::DEMO_RESET_PARAM, '_wpnonce' ] ) );
240 + exit;
241 + }
242 +
243 + /**
183 244 * Nonce action guarding the run-once request.
184 245 */
185 246 public const RUN_ONCE_NONCE = 'code_snippets_run_once';
186 247
187 248 /**
188 - * Run a single-use snippet, when asked to by the snippets list.
249 + * Run a single-use snippet, when asked by the snippets list.
189 250 *
190 251 * Activating the snippet is all that is required: single-use snippets are
191 252 * executed and then deactivated again on the next page load, so redirecting
192 - * afterwards both runs the code and returns the snippet to its resting
253 + * afterward both runs the code and returns the snippet to its resting
193 254 * state. This mirrors what the list table did before the snippets list
194 255 * moved to the REST API, at which point the button was left pointing at a
195 256 * URL that nothing handled.
196 257 *
@@ -231,9 +292,9 @@
231 292 }
232 293
233 294 // Safe mode skips execution, so activating here would leave the snippet on
234 295 // without ever running it, behind a false success notice. Report it instead.
235 - if ( \Code_Snippets\Integration\Evaluate_Functions::is_safe_mode_active() ) {
296 + if ( Evaluate_Functions::is_safe_mode_active() ) {
236 297 wp_safe_redirect(
237 298 add_query_arg(
238 299 [ 'result' => 'run-once-safe-mode' ],
239 300 remove_query_arg( [ 'action', 'snippet', 'network', '_wpnonce', 'result' ] )
@@ -254,11 +315,31 @@
254 315 exit;
255 316 }
256 317
257 318 /**
319 + * Send a fresh Run Once nonce with each Heartbeat, so a page left open past
320 + * the nonce lifetime can still run a snippet.
321 + *
322 + * @param mixed $response Heartbeat response.
323 + *
324 + * @return array<string, mixed>
325 + */
326 + public function refresh_run_once_nonce( $response ): array {
327 + $response = is_array( $response ) ? $response : [];
328 +
329 + if ( code_snippets()->current_user_can() ) {
330 + $response['code_snippets_run_once_nonce'] = wp_create_nonce( self::RUN_ONCE_NONCE );
331 + }
332 +
333 + return $response;
334 + }
335 +
336 + /**
258 337 * Executed when the admin page is loaded.
259 338 */
260 339 public function load() {
340 + $this->maybe_reset_demos();
341 +
261 342 parent::load();
262 343
263 344 $this->handle_run_once();
264 345 $this->screen_options->load();
@@ -263,12 +344,19 @@
263 344 $this->handle_run_once();
264 345 $this->screen_options->load();
265 346
266 347 if ( $this->screen_options->is_upsell_view() ) {
348 + $subpage = $this->screen_options->get_current_subpage();
349 +
350 + if ( in_array( $subpage, [ 'cloud-library', 'blueprints', 'ai-agent' ], true ) ) {
351 + $contextual_help = new Contextual_Help( $subpage );
352 + $contextual_help->load();
353 + }
354 +
267 355 return;
268 356 }
269 357
270 - $contextual_help = new Contextual_Help( 'edit' );
358 + $contextual_help = new Contextual_Help( 'manage' );
271 359 $contextual_help->load();
272 360 }
273 361
274 362 /**