Skip to content

Feature/gh265 improve onboard flow#273

Merged
gin0115 merged 9 commits into
trunkfrom
feature/gh265-improve-onboard-flow
Nov 21, 2025
Merged

Feature/gh265 improve onboard flow#273
gin0115 merged 9 commits into
trunkfrom
feature/gh265-improve-onboard-flow

Conversation

@gin0115

@gin0115 gin0115 commented Nov 20, 2025

Copy link
Copy Markdown
Collaborator

Changes proposed in this Pull Request

This pull request introduces a refactor and enhancement of the onboarding and setup wizard logic for the plugin, centralizing state management in the Settings class and improving user experience for onboarding and wizard steps. It also adds UI improvements and code cleanups related to the wizard flow.

Onboarding & Wizard Logic Refactor:

  • The onboarding and setup wizard state (step, completion, status) is now managed via new methods and constants in the Settings class, replacing previous direct option usage and scattered logic. This includes new methods like get_setup_wizard_step, update_setup_wizard_step, is_wizard_completed, set_wizard_completed, set_onboarding_status, and get_onboarding_status. [1] [2] [3]
  • All wizard step transitions and completion checks throughout the codebase now use the centralized Settings methods, replacing previous logic and constants in Setup_Wizard. This includes changes to how steps are marked as complete, how onboarding is triggered, and how the wizard is rerun or redirected. [1] [2] [3] [4] [5] [6] [7] [8] [9]

Onboarding Experience Improvements:

  • Adds a mechanism to automatically trigger the onboarding wizard for new users, including a body class (iawmlf-onboarding-active) for styling and a redirect to the wizard if onboarding is pending. [1] [2] [3]
  • The onboarding status is now set and checked more robustly during plugin activation and wizard completion, preventing duplicate onboarding and ensuring the correct flow. [1] [2] [3]

Wizard UI & Usability:

  • The wizard footer now disables the "Previous Step" button on the first step for clarity, and the allowed HTML tags for wizard fields now include the disabled attribute. [1] [2]
  • The wizard step-1 template has been simplified, removing credential validation UI logic and unused variables, and updating instructional text.

Code Cleanup & Consistency:

  • Redundant constants and direct option accesses in Setup_Wizard have been removed, and step handling logic has been simplified to use the new settings methods. [1] [2]
  • Option cleanup in Settings::clear_all_options() now removes onboarding-related options.

Styling Enhancements:

  • Adds specific CSS for the onboarding active state to visually distinguish the onboarding flow in the admin area.

Testing instructions

Mentions #265

Summary by CodeRabbit

  • New Features

    • Post-activation onboarding flow with persistent onboarding state and wizard step tracking.
    • Ability to force-reschedule background post-scan tasks to run sooner with high priority.
  • Improvements

    • Simplified setup wizard UI and flow; disabled "Previous" on first step, removed several optional controls, and adjusted UI text.
    • Dashboard gains onboarding overview, refreshed link statistics, new status/navigation elements, and full-screen onboarding overlay styling.
  • Chores

    • Distribution packaging now includes index.php.
  • Documentation

    • Added dashboard cache-expiry filter docs.
  • Tests

    • Updated and added tests to cover onboarding state and rescheduling behavior.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai

coderabbitai Bot commented Nov 20, 2025

Copy link
Copy Markdown

Walkthrough

Centralizes setup-wizard and onboarding state into the Settings API (new keys, getters/setters, onboarding status and date); updates Setup_Wizard to use Settings for step/state persistence, adds onboarding trigger and body-class helper, and adjusts step handling; activation now sets onboarding pending; Settings::clear_all_options expanded and tests updated; wizard templates simplified (several UI fields removed or replaced with hidden inputs) and onboarding CSS added; new Dashboard_Statistics class and onboarding dashboard templates wired into dashboard rendering; Scan_Posts_Event gained force_add_to_action_scheduler() with a new test.

Possibly related PRs

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main focus of the pull request: improving the onboarding flow through centralized state management and wizard refactoring.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings

Comment @coderabbitai help to get the list of available commands and usage tips.

@gin0115 gin0115 changed the base branch from release/1.3.4 to trunk November 20, 2025 17:29

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/Dashboard/Setup_Wizard.php (2)

257-288: Previous‑step handler should not call step submission handlers

handle_previous_step() currently invokes the step submission handlers even when the user is going back:

// Upadate the current step to previous for rendering.
if ( 'step-3' === $current_step ) {
    $this->handle_step_3();
} elseif ( 'step-2' === $current_step ) {
    $this->handle_step_2();
}
...
Settings::update_setup_wizard_step( $previous_step );

Problems:

  • handle_step_2() and handle_step_3() expect iawmlf-next-step and will add an error notice (“Next step is not set”) when navigating backwards.
  • They also re‑run side‑effects (updating options, marking onboarding complete, etc.) on a “Previous” click, which is unexpected.

You can simplify handle_previous_step() to only move the wizard state back:

-        // Upadate the current step to previous for rendering.
-        if ( 'step-3' === $current_step ) {
-            $this->handle_step_3();
-        } elseif ( 'step-2' === $current_step ) {
-            $this->handle_step_2();
-        }
+        // Update the current step to the previous one for rendering only.

The subsequent logic that computes $previous_step and calls Settings::update_setup_wizard_step( $previous_step ); is already sufficient.


304-337: Correct the hidden field's actual value and confirm the fix

The hidden field doesn't have a literal "0|1" value—it's actually rendered dynamically as either "1" or "0" via Settings::is_link_processing_enabled( true ). However, the underlying issue is valid: isset() only checks presence, not the value, so it always returns true on submission, ignoring any disabled state from the previous page visit.

The suggested fix is correct and will properly respect both enabled and disabled states:

$is_active          = isset( $_POST['iawmlf_wizard_activate_link_fixer'] )
    && '1' === sanitize_text_field( wp_unslash( $_POST['iawmlf_wizard_activate_link_fixer'] ) );

This ensures that PROCESS_LINKS reflects the user's actual choice, not a forced true whenever post types are selected.

🧹 Nitpick comments (5)
templates/admin/wizard/step-2.php (3)

22-22: Escape hidden input value and double‑check the default

The hidden iawmlf_wizard_activate_link_fixer field currently prints the raw '1'/'0' value. It’s low risk, but you can harden it and make the intent clearer:

-<input type="hidden" name="iawmlf_wizard_activate_link_fixer" value="<?php echo Settings::is_link_processing_enabled( true ) ? '1' : '0'; ?>" />
+<input type="hidden" name="iawmlf_wizard_activate_link_fixer" value="<?php echo esc_attr( Settings::is_link_processing_enabled( true ) ? '1' : '0' ); ?>" />

Also worth confirming that is_link_processing_enabled( true ) is meant to default to “enabled” on a fresh install; if you want the wizard to start from “off”, pass false instead.


28-36: Clean up “??” prefixes in user‑facing copy

Both the intro and description strings start with literal "??", which will look like placeholder/debug markers in the UI:

esc_html_e( '?? You can set the Link Fixer...', ... );
esc_html_e( '?? Select the post types...', ... );

If this isn’t intentional branding, please drop the prefix before release.


32-47: Improve label association and grouping for post‑type checkboxes

The label uses for="iawmlf_wizard_post_types", but there is no element with that id; only the checkboxes exist. For better semantics/a11y, consider:

  • Wrapping the checkboxes in a <fieldset> with a <legend> instead of a free‑floating <label>, or
  • Giving the checkbox group an explicit id and updating for to match.

Not a blocker, but a small quality improvement.

src/Dashboard/Setup_Wizard.php (1)

70-82: add_onboarding_body_class: scope and sanitization

The body‑class helper is straightforward, but two small considerations:

  • It triggers on any admin page with ?iawmlf_onboarding=…, not just the wizard. That’s probably fine if your CSS is robust, but worth being aware of.
  • Since only the presence of the query arg matters, you might prefer isset( $_GET['iawmlf_onboarding'] ) for consistency with maybe_trigger_onboarding_wizard().

No functional blockers here.

src/Settings/Settings.php (1)

450-532: Align get_onboarding_status() docblock and consider legacy value mapping

The onboarding helpers are a good abstraction, but there are two small issues:

  1. Docblock vs implementation mismatch

Docblock says:

 * @param string $default_value Optional default value if not set. Default ONBOARDING_PENDING_OPTION.

But the signature uses self::ONBOARDING_COMPLETED_OPTION as the default:

public static function get_onboarding_status( string $default_value = self::ONBOARDING_COMPLETED_OPTION ): string {

Either the doc or the default should be updated so they agree (and accurately reflect the intended default for “no stored value” cases).

  1. Potential backwards‑compat with legacy option values

get_onboarding_status() only recognizes the two new string constants as valid:

$state = get_option( self::POST_ACTIVATION_ONBOARDING_KEY, $default_value );
return in_array( $state, array( self::ONBOARDING_COMPLETED_OPTION, self::ONBOARDING_PENDING_OPTION ), true )
    ? $state
    : self::ONBOARDING_PENDING_OPTION;

If any previous plugin versions ever stored boolean/0/1 or alternative strings under POST_ACTIVATION_ONBOARDING_KEY, those installs will now see ONBOARDING_PENDING_OPTION and be treated as “needs onboarding” again.

If that’s a realistic history for this option, consider a small compatibility shim, e.g.:

if ( ! in_array( $state, array( self::ONBOARDING_COMPLETED_OPTION, self::ONBOARDING_PENDING_OPTION ), true ) ) {
    // Legacy mapping: truthy -> completed, falsy -> pending.
    if ( in_array( $state, array( '1', 1, true ), true ) ) {
        $state = self::ONBOARDING_COMPLETED_OPTION;
    } else {
        $state = self::ONBOARDING_PENDING_OPTION;
    }
}

If you’re sure earlier versions only ever used the new string values, then just fixing the docblock is enough.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 02c39df and 610514b.

📒 Files selected for processing (13)
  • .distignore (0 hunks)
  • assets/css/src/admin/_wizard.scss (1 hunks)
  • functions.php (1 hunks)
  • src/Dashboard/Settings_Page.php (2 hunks)
  • src/Dashboard/Setup_Wizard.php (10 hunks)
  • src/Settings/Settings.php (4 hunks)
  • src/Util/Esc.php (1 hunks)
  • templates/admin/wizard/footer.php (1 hunks)
  • templates/admin/wizard/step-1.php (1 hunks)
  • templates/admin/wizard/step-2.php (2 hunks)
  • templates/admin/wizard/step-3.php (1 hunks)
  • tests/Settings/Test_Settings.php (2 hunks)
  • tests/bootstrap.php (1 hunks)
💤 Files with no reviewable changes (1)
  • .distignore
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-11-17T23:43:45.310Z
Learnt from: gin0115
Repo: a8cteam51/internet-archive-wayback-machine-link-fixer PR: 250
File: src/Settings/Settings.php:301-311
Timestamp: 2025-11-17T23:43:45.310Z
Learning: In the Internet Archive Wayback Machine Link Fixer plugin (file: src/Settings/Settings.php), the `iawmlf_add_own_content_to_wayback_machine` filter is intentionally applied after the production environment check in the `add_own_links()` method. This prevents the filter from overriding the hard block on archiving in non-production environments (staging, development, local), which is expected behavior to ensure staging/dev sites are never archived.

Applied to files:

  • templates/admin/wizard/step-3.php
🧬 Code graph analysis (7)
tests/bootstrap.php (1)
src/Settings/Settings.php (1)
  • Settings (22-564)
templates/admin/wizard/step-3.php (1)
src/Settings/Settings.php (2)
  • Settings (22-564)
  • add_own_links (315-327)
templates/admin/wizard/step-2.php (1)
src/Settings/Settings.php (2)
  • Settings (22-564)
  • is_link_processing_enabled (89-91)
tests/Settings/Test_Settings.php (1)
src/Settings/Settings.php (1)
  • Settings (22-564)
src/Dashboard/Setup_Wizard.php (2)
src/Settings/Settings.php (7)
  • Settings (22-564)
  • is_wizard_completed (483-485)
  • get_onboarding_status (527-532)
  • update_setup_wizard_step (472-474)
  • set_onboarding_status (509-516)
  • set_wizard_completed (496-498)
  • get_setup_wizard_step (459-461)
src/Util/Environmental.php (2)
  • Environmental (20-38)
  • is_production (27-37)
src/Dashboard/Settings_Page.php (1)
src/Settings/Settings.php (2)
  • Settings (22-564)
  • is_wizard_completed (483-485)
functions.php (2)
src/Migration/Migrations.php (2)
  • Migrations (20-79)
  • up (39-57)
src/Settings/Settings.php (4)
  • Settings (22-564)
  • get_onboarding_status (527-532)
  • is_wizard_completed (483-485)
  • set_onboarding_status (509-516)
🔇 Additional comments (15)
src/Util/Esc.php (1)

41-46: LGTM! Enabling disabled state for wizard buttons.

The addition of the disabled attribute to the button's allowed tags correctly supports the new wizard navigation behavior where certain buttons (e.g., the Previous button on the first step) need to be disabled. The implementation follows the proper wp_kses pattern for boolean attributes.

assets/css/src/admin/_wizard.scss (1)

1-14: LGTM! Onboarding overlay styling is well-implemented.

The CSS correctly creates a full-screen overlay for onboarding mode with proper positioning, z-index, and centered content layout.

tests/bootstrap.php (1)

68-70: LGTM! Test setup correctly bypasses onboarding.

The test bootstrap appropriately marks onboarding as completed to prevent interference with test execution. This aligns with the new Settings-based onboarding state management.

src/Dashboard/Settings_Page.php (2)

100-100: LGTM! Wizard state check correctly migrated to Settings API.

The change from Setup_Wizard::is_setup_complete() to Settings::is_wizard_completed() aligns with the centralized state management approach introduced in this PR.


172-172: LGTM! Consistent migration to Settings-based wizard status.

This change properly uses the centralized Settings API for wizard completion checks, maintaining consistency across the codebase.

functions.php (1)

103-114: LGTM! Onboarding state management correctly integrated into activation.

The activation hook now properly manages onboarding state:

  • Runs migrations first
  • Checks existing onboarding/wizard status to avoid overwriting
  • Sets onboarding to pending for new installations

The logic correctly prevents re-triggering onboarding for users who have already completed it.

templates/admin/wizard/step-3.php (1)

25-25: Verify: Auto Archiver toggle removed from user control.

The Auto Archiver enable/disable UI was replaced with a hidden input that reads the current state from settings. This means users can no longer toggle the Auto Archiver setting within the wizard—they can only configure which post types to archive.

Is this intentional? If users should be able to enable/disable Auto Archiver during setup, the toggle UI should be retained.

tests/Settings/Test_Settings.php (2)

300-304: LGTM! Test coverage expanded for new onboarding/wizard options.

The test now properly validates that the new onboarding and wizard-related settings are cleared by Settings::clear_all_options(), ensuring clean uninstall behavior.


325-329: LGTM! Assertions complete for new option cleanup.

The assertions correctly verify that all new onboarding/wizard options are removed after calling clear_all_options().

templates/admin/wizard/footer.php (1)

32-34: Previous button correctly disabled on first step — verification complete.

The disabled attribute is already included in Esc::wizard_allowed_tags() for the button element (see src/Util/Esc.php), so the attribute will not be stripped by wp_kses(). The implementation is correct and requires no changes.

src/Dashboard/Setup_Wizard.php (3)

295-297: Step‑1 handler is clean and aligned with centralized state

handle_step_1() simply advances the wizard state via Settings::update_setup_wizard_step( 'step-2' );, which fits the new centralized settings approach and the simplified step‑1 UI.

No issues spotted here.


353-366: Step‑3 handler looks consistent with Settings/onboarding model

The step‑3 submission logic:

  • Reads iawmlf_wizard_activate_auto_archiver and allowed post types.
  • Updates ALLOW_OWN_CONTENT_SUBMISSIONS, ALLOWED_OWN_CONTENT_POST_TYPES, and ROUTINELY_UPDATE_WAYBACK_MACHINE.
  • Marks the wizard as complete and sets onboarding status to completed.

Given add_own_links() already force‑disables own‑content submissions in non‑production via Environmental::is_production(), this is consistent and safe. No changes needed here.


435-467: Using Settings::get_setup_wizard_step() for step data is a nice centralization

get_step_data() now pulls the current step from Settings::get_setup_wizard_step('step-1') and applies the same environment‑based skipping for step‑3 before computing template, next, and progress. This keeps wizard state in one place and avoids direct option access here.

Looks good.

src/Settings/Settings.php (2)

29-45: Option and onboarding constants are clear and consistent

Introducing explicit keys for wizard/onboarding state and discrete status constants (ONBOARDING_COMPLETED_OPTION, ONBOARDING_PENDING_OPTION) keeps the Settings API coherent and self‑documenting. No issues here.

Also applies to: 58-61


541-563: Expanded clear_all_options() coverage looks good

Adding the onboarding and wizard keys (plus MINIMUM_CHECKS_BEFORE_BROKEN and LINK_CHECK_DURATION_IN_DAYS) to clear_all_options() ensures a true reset of plugin state:

delete_option( self::POST_ACTIVATION_ONBOARDING_KEY );
delete_option( self::MINIMUM_CHECKS_BEFORE_BROKEN );
delete_option( self::LINK_CHECK_DURATION_IN_DAYS );
delete_option( self::SETUP_WIZARD_STEP_KEY );
delete_option( self::SETUP_WIZARD_COMPLETED_KEY );

This is a solid cleanup improvement.

Comment thread src/Dashboard/Setup_Wizard.php

<div class="iawmlf-wizard__content__header">
<h2><?php esc_html_e( 'Step 1: Configure the Wayback Machine API', 'internet-archive-wayback-machine-link-fixer' ); ?></h2>
<h2><?php esc_html_e( '??Step 1: Configure the Wayback Machine API', 'internet-archive-wayback-machine-link-fixer' ); ?></h2>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Critical: Remove debugging placeholder "??" from heading.

The "??" prefix appears to be debugging or placeholder text that was accidentally left in the translatable string. This will be visible to users in the wizard UI.

Apply this diff:

-	<h2><?php esc_html_e( '??Step 1: Configure the Wayback Machine API', 'internet-archive-wayback-machine-link-fixer' ); ?></h2>
+	<h2><?php esc_html_e( 'Step 1: Configure the Wayback Machine API', 'internet-archive-wayback-machine-link-fixer' ); ?></h2>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<h2><?php esc_html_e( '??Step 1: Configure the Wayback Machine API', 'internet-archive-wayback-machine-link-fixer' ); ?></h2>
<h2><?php esc_html_e( 'Step 1: Configure the Wayback Machine API', 'internet-archive-wayback-machine-link-fixer' ); ?></h2>
🤖 Prompt for AI Agents
In templates/admin/wizard/step-1.php around line 22, the heading contains a
debugging placeholder "??" inside the translatable string; remove the "??" so
the string reads normally (e.g. "Step 1: Configure the Wayback Machine API") and
ensure esc_html_e uses the corrected string to avoid showing the placeholder to
users and keep translation keys unchanged.

Comment on lines +26 to 27
<p><?php esc_html_e( '??To archive more than 4000 links from your site to the Wayback Machine per day, you\'ll need a free archive.org account. Once you have your account, enter the API Access Key and Secret Key below. (Optional)', 'internet-archive-wayback-machine-link-fixer' ); ?>
<br /><a href="https://archive.org/account/s3.php" target="_blank"><?php esc_html_e( 'Get your API keys here.', 'internet-archive-wayback-machine-link-fixer' ); ?></a></p>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Critical: Remove debugging placeholder "??" from intro text.

The "??" prefix appears to be debugging or placeholder text that was accidentally left in the translatable string. This will be visible to users in the wizard.

Apply this diff:

-	<p><?php esc_html_e( '??To archive more than 4000 links from your site to the Wayback Machine per day, you\'ll need a free archive.org account. Once you have your account, enter the API Access Key and Secret Key below. (Optional)', 'internet-archive-wayback-machine-link-fixer' ); ?>
+	<p><?php esc_html_e( 'To archive more than 4000 links from your site to the Wayback Machine per day, you\'ll need a free archive.org account. Once you have your account, enter the API Access Key and Secret Key below. (Optional)', 'internet-archive-wayback-machine-link-fixer' ); ?>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<p><?php esc_html_e( '??To archive more than 4000 links from your site to the Wayback Machine per day, you\'ll need a free archive.org account. Once you have your account, enter the API Access Key and Secret Key below. (Optional)', 'internet-archive-wayback-machine-link-fixer' ); ?>
<br /><a href="https://archive.org/account/s3.php" target="_blank"><?php esc_html_e( 'Get your API keys here.', 'internet-archive-wayback-machine-link-fixer' ); ?></a></p>
<p><?php esc_html_e( 'To archive more than 4000 links from your site to the Wayback Machine per day, you\'ll need a free archive.org account. Once you have your account, enter the API Access Key and Secret Key below. (Optional)', 'internet-archive-wayback-machine-link-fixer' ); ?>
<br /><a href="https://archive.org/account/s3.php" target="_blank"><?php esc_html_e( 'Get your API keys here.', 'internet-archive-wayback-machine-link-fixer' ); ?></a></p>
🤖 Prompt for AI Agents
In templates/admin/wizard/step-1.php around lines 26 to 27, the translatable
paragraph includes a stray debugging placeholder "??" at the start of the
string; remove the "??" so the esc_html_e call begins with the proper
user-facing sentence, preserving the existing translation domain and HTML
structure (keep the br and anchor unchanged) so the visible text no longer shows
the debugging characters.

…allow link fixing, it will bump the first scan of own content to instant time with a priority of 0 to get the process going

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 7

♻️ Duplicate comments (1)
src/Dashboard/Setup_Wizard.php (1)

113-135: Onboarding redirect lacks capability and AJAX protections (duplicate concern).

This method redirects every admin request without checking:

  1. Whether the request is AJAX or cron (breaking background flows)
  2. Whether the user has the required capability (trapping non-admins in redirect loops)

The manage_options check at line 147 only protects the submenu registration, not this redirect which fires earlier.

Add protections at the start of the method:

 public function maybe_trigger_onboarding_wizard(): void {
+	if ( wp_doing_ajax() || ( defined( 'DOING_CRON' ) && DOING_CRON ) ) {
+		return;
+	}
+
+	if ( ! current_user_can( 'manage_options' ) ) {
+		return;
+	}
+
 	if ( Settings::ONBOARDING_COMPLETED_OPTION === Settings::get_onboarding_status() || Settings::is_wizard_completed() ) {

Based on learnings (past review comment).

🧹 Nitpick comments (3)
tests/Event/Test_Scan_Posts_Event.php (3)

37-38: Prefer Action Scheduler APIs over direct SQL queries.

Direct SQL queries tightly couple the test to the Action Scheduler database schema. If the schema changes in a future version, these queries will break. Action Scheduler provides API functions to query scheduled actions.

Consider using Action Scheduler's API functions instead:

-// Get the time of the scheduled action.
-$actions = $GLOBALS['wpdb']->get_results( "SELECT * FROM {$GLOBALS['wpdb']->prefix}actionscheduler_actions WHERE hook='iawmlf_scan_existing_posts' AND status='pending'" );
-$time_1 = $actions[0]->scheduled_date_gmt;
+// Get the scheduled action using Action Scheduler API.
+$time_1 = as_next_scheduled_action( 'iawmlf_scan_existing_posts', array(), 'iawmlf_event' );

Similarly for the second query. This would make the test more maintainable and resilient to Action Scheduler updates.

Also applies to: 44-46


49-49: String comparison of timestamps may be fragile.

Comparing datetime strings directly (line 49) assumes consistent formatting and can fail if timezone handling or storage format changes. Consider converting to Unix timestamps for more robust comparison.

If using as_next_scheduled_action() as suggested above, the comparison becomes simpler since it returns Unix timestamps:

-$this->assertTrue( $time_2 < $time_1, 'The scheduled time was not updated to an earlier time.' );
+$this->assertLessThan( $time_1, $time_2, 'The scheduled time was not updated to an earlier time.' );

29-51: Consider adding test cleanup to remove scheduled actions.

The test schedules actions but doesn't clean them up in a tearDown method. While this may not affect subsequent tests if the database is reset between tests, explicit cleanup improves test isolation and makes the test's lifecycle clearer.

Add a tearDown method:

public function tearDown(): void {
	parent::tearDown();
	// Clean up scheduled actions.
	if ( function_exists( 'as_unschedule_all_actions' ) ) {
		as_unschedule_all_actions( 'iawmlf_scan_existing_posts' );
	}
}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 610514b and 9f1db35.

📒 Files selected for processing (4)
  • src/Dashboard/Setup_Wizard.php (11 hunks)
  • src/Event/Scan_Posts_Event.php (1 hunks)
  • tests/Event/Test_Scan_Own_Posts_Event.php (1 hunks)
  • tests/Event/Test_Scan_Posts_Event.php (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • tests/Event/Test_Scan_Own_Posts_Event.php
🧰 Additional context used
🧬 Code graph analysis (3)
src/Event/Scan_Posts_Event.php (1)
src/Settings/Settings.php (2)
  • Settings (22-564)
  • should_scan_existing_posts (210-216)
tests/Event/Test_Scan_Posts_Event.php (2)
src/Event/Scan_Posts_Event.php (3)
  • Scan_Posts_Event (24-174)
  • add_to_action_scheduler (97-124)
  • force_add_to_action_scheduler (72-90)
src/Settings/Settings.php (1)
  • Settings (22-564)
src/Dashboard/Setup_Wizard.php (2)
src/Event/Scan_Posts_Event.php (2)
  • Scan_Posts_Event (24-174)
  • force_add_to_action_scheduler (72-90)
src/Settings/Settings.php (8)
  • Settings (22-564)
  • is_wizard_completed (483-485)
  • get_onboarding_status (527-532)
  • update_setup_wizard_step (472-474)
  • should_scan_existing_posts (210-216)
  • set_onboarding_status (509-516)
  • set_wizard_completed (496-498)
  • get_setup_wizard_step (459-461)
🔇 Additional comments (8)
src/Event/Scan_Posts_Event.php (1)

65-90: No issues found with as_enqueue_async_action() call.

The function signature is as_enqueue_async_action( string $hook, array $args = array(), string $group = '', bool $unique = false, int $priority = 10 ), and the call on line 89 correctly passes all five arguments in the expected order with matching types: self::HANDLE (hook), array() (args), 'iawmlf_event' (group), false (unique), and 0 (priority). The implementation is correct.

src/Dashboard/Setup_Wizard.php (7)

15-15: LGTM!

The import is necessary for the new scan triggering functionality in handle_step_2().


26-32: LGTM!

The private visibility for these constants is appropriate since they're only used internally within the Setup_Wizard class.


52-58: LGTM!

Proper deprecation notice added, and the delegation to Settings::is_wizard_completed() aligns with the centralized state management approach.


91-91: LGTM!

The migration to Settings::is_wizard_completed() is consistent with the centralized state management approach.


288-288: LGTM!

The migration to Settings::update_setup_wizard_step() is consistent with the centralized state management approach.

Also applies to: 297-297


375-377: LGTM!

The migration to Settings methods (update_setup_wizard_step, set_wizard_completed, set_onboarding_status) is consistent with the centralized state management approach.


448-448: LGTM!

The migration to Settings::get_setup_wizard_step() is consistent with the centralized state management approach, and the default value is appropriate.

Comment thread src/Dashboard/Setup_Wizard.php
Comment thread src/Dashboard/Setup_Wizard.php Outdated
Comment thread src/Dashboard/Setup_Wizard.php Outdated
Comment thread src/Dashboard/Setup_Wizard.php Outdated
Comment thread src/Dashboard/Setup_Wizard.php Outdated
Comment thread src/Dashboard/Setup_Wizard.php Outdated
Comment thread src/Event/Scan_Posts_Event.php Outdated
…s since install and we still have posts to process

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
src/Dashboard/Dashboard_Notifications.php (1)

88-101: Fix array formatting to resolve PHPCS errors on dashboard widget data

The new entries are fine functionally, but PHPCS is complaining about arrow alignment and the missing trailing comma on the last item in this multi‑line array.

-				'iawmlf_failed_check_count'      => Settings::get_failed_count(),
-				'iawmlf_onboarding_details'      => Dashboard_Statistics::get_onboarding_statistics(),
-				'iawmlf_link_stats'         => Dashboard_Statistics::get_link_statistics()
+				'iawmlf_failed_check_count'      => Settings::get_failed_count(),
+				'iawmlf_onboarding_details'      => Dashboard_Statistics::get_onboarding_statistics(),
+				'iawmlf_link_stats'              => Dashboard_Statistics::get_link_statistics(),

Optionally, you may also add a use Internet_Archive\Wayback_Machine_Link_Fixer\Dashboard\Dashboard_Statistics; at the top for consistency with the other Dashboard classes, though it isn’t required for correctness.

templates/admin/dashboard/page.php (1)

27-44: Use the pre‑normalized total link count when passing data to the onboarding template

You already defensively normalize total_links into $iawmlf_total_links_count using ?? 0, but the onboarding call still accesses $iawmlf_link_stats['total_links'] directly. Using the normalized variable avoids any potential undefined‑index notices and keeps the logic DRY.

-							iawmlf_render_template(
-								'admin/dashboard/onboarding.php',
-								array(
-									'iawmlf_onboarding_details' => $iawmlf_onboarding_details,
-									'iawmlf_total_links_count' => $iawmlf_link_stats['total_links'],
-									'iawmlf_link_table' => \Internet_Archive\Wayback_Machine_Link_Fixer\Dashboard\Report_Page::get_page_url(),
-								)
-							);
+							iawmlf_render_template(
+								'admin/dashboard/onboarding.php',
+								array(
+									'iawmlf_onboarding_details'  => $iawmlf_onboarding_details,
+									'iawmlf_total_links_count'   => $iawmlf_total_links_count,
+									'iawmlf_link_table'          => \Internet_Archive\Wayback_Machine_Link_Fixer\Dashboard\Report_Page::get_page_url(),
+								)
+							);

As a future clean‑up (no change needed for this PR), you might also consider updating the widget template to accept a numeric $iawmlf_total_links_count instead of constructing a dummy array via array_fill(), which would simplify the data flow.

Also applies to: 112-129

src/Settings/Settings.php (1)

567-589: Ensure clear_all_options() also deletes ONBOARDING_DATE_KEY

clear_all_options() has been expanded to remove several onboarding-related options, which is good:

delete_option( self::POST_ACTIVATION_ONBOARDING_KEY );
delete_option( self::SETUP_WIZARD_STEP_KEY );
delete_option( self::SETUP_WIZARD_COMPLETED_KEY );

However, ONBOARDING_DATE_KEY is not deleted, so a previous onboarding date will linger after a “clear all options” call and may continue to influence onboarding statistics.

Consider adding:

 		delete_option( self::SETUP_WIZARD_COMPLETED_KEY );
+		delete_option( self::ONBOARDING_DATE_KEY );

to keep cleanup symmetrical with the new onboarding-related state.

♻️ Duplicate comments (5)
src/Dashboard/Setup_Wizard.php (5)

71-83: Fix typo and static/instance mismatch in add_onboarding_body_class()

  • Docblock typo: "oboarding" → "onboarding".
  • Method is declared static but registered as an instance callback (array( $this, 'add_onboarding_body_class' )); either:
    • Drop static on the method, or
    • Register it as array( __CLASS__, 'add_onboarding_body_class' ).

Functionally it works today, but tightening this avoids confusion.


113-143: Align onboarding redirect capability with Settings and ensure minimal scope

The added AJAX/cron guards are good, but the capability check is hard-coded:

if ( ! current_user_can( 'manage_options' ) ) {
    return;
}

Elsewhere, capabilities are centralized via Settings::get_reporting_page_capability(). For consistency and extensibility (e.g., sites that adjust capabilities via the filter), this should use that helper:

-		if ( ! current_user_can( 'manage_options' ) ) {
+		if ( ! current_user_can( Settings::get_reporting_page_capability() ) ) {
 			return;
 		}

That keeps onboarding redirects aligned with whatever capability the plugin exposes for its settings/reporting pages.


266-297: “Previous” navigation still calls step handlers and will trigger validation errors

When handling a "Previous" click, handle_previous_step() still invokes the step handlers:

// Upadate the current step to previous for rendering.
if ( 'step-3' === $current_step ) {
    $this->handle_step_3();
} elseif ( 'step-2' === $current_step ) {
    $this->handle_step_2();
}

But handle_step_2() and handle_step_3() both expect iawmlf-next-step in $_POST and emit an error if it’s missing. On a "Previous" submit, only iawmlf-previous-step is present, so you’ll get spurious “Next step is not set” notices instead of moving back a step.

For backward navigation, you generally just need to adjust the stored step, not re-run forward validation. Consider removing these calls:

-		// Upadate the current step to previous for rendering.
-		if ( 'step-3' === $current_step ) {
-			$this->handle_step_3();
-		} elseif ( 'step-2' === $current_step ) {
-			$this->handle_step_2();
-		}
-

The rest of handle_previous_step() already computes and stores the prior step correctly.


326-370: Step‑2 overwrites user settings on rerun and mis-triggers scan scheduling

Several issues in handle_step_2():

  1. Unconditional overwrite of FIXER_OPTION on rerun
update_option( Settings::FIXER_OPTION, Settings::FIXER_OPTION_REPLACE_LINK );

This resets the fixer behavior every time the wizard is rerun, even if the user customized it elsewhere. Typically, you either:

  • Only set a default the first time (when onboarding is pending), or
  • Drive the value from a form field.

Example of “initial setup only” behavior:

-	update_option( Settings::FIXER_OPTION, Settings::FIXER_OPTION_REPLACE_LINK );
+	// Set default fixer option only during initial onboarding.
+	if ( Settings::ONBOARDING_PENDING_OPTION === Settings::get_onboarding_status() ) {
+		update_option( Settings::FIXER_OPTION, Settings::FIXER_OPTION_REPLACE_LINK );
+	}
  1. Inverted onboarding check for Scan_Posts_Event::force_add_to_action_scheduler()

Comment says:

// If we are still in initial onboarding, force the scan own content to true.

but the condition is:

if ( Settings::ONBOARDING_PENDING_OPTION !== Settings::get_onboarding_status() ) {
    Scan_Posts_Event::force_add_to_action_scheduler();
}

This runs the scan when not in initial onboarding (i.e., most often on reruns), and skips it for first-time users. The condition should be equality:

-		if ( Settings::ONBOARDING_PENDING_OPTION !== Settings::get_onboarding_status() ) {
+		if ( Settings::ONBOARDING_PENDING_OPTION === Settings::get_onboarding_status() ) {
 			// Force the scan own content to trigger early.
 			Scan_Posts_Event::force_add_to_action_scheduler();
 		}
  1. SCAN_EXISTING_POSTS recalculation is slightly opaque
update_option( Settings::SCAN_EXISTING_POSTS, Settings::should_scan_existing_posts( $process_links ) );

Here, $process_links (a boolean) is passed as the default to should_scan_existing_posts(), which itself checks link processing and another option. It works, but the intent is harder to follow. Explicitly setting SCAN_EXISTING_POSTS based on $process_links (or a dedicated field) would be clearer.

Overall, I’d recommend:

  • Guarding FIXER_OPTION updates so reruns don’t clobber custom settings.
  • Fixing the inverted onboarding condition for Scan_Posts_Event.

377-403: Step‑3 unconditionally forces routine updates and may overwrite user preferences

In handle_step_3():

update_option( Settings::ROUTINELY_UPDATE_WAYBACK_MACHINE, true );

This hard-codes the “routinely update Wayback Machine” behavior to true every time the wizard completes step‑3, even if the user previously disabled it and reruns the wizard later.

As with the fixer option in step‑2, this is better treated as an initial default or driven by a form field. For minimal change:

-	update_option( Settings::ROUTINELY_UPDATE_WAYBACK_MACHINE, true );
+	// Set a default only if the option has never been configured.
+	if ( false === get_option( Settings::ROUTINELY_UPDATE_WAYBACK_MACHINE, false ) ) {
+		update_option( Settings::ROUTINELY_UPDATE_WAYBACK_MACHINE, true );
+	}

That avoids silently overriding existing user preferences on reruns while still providing a sensible default for first-time setups.

🧹 Nitpick comments (6)
README.md (1)

811-829: Replace hard tabs with spaces in new code examples to satisfy markdownlint

markdownlint is flagging MD010 (hard tabs) on the return lines in the two new cache-expiry filter examples. Converting those to spaces will clear the warning and keep formatting consistent.

-```php
-add_filter( 'iawmlf_dashboard_link_stats_cache_expiry', function( int $cache_expiry ): int {
-	return 5 * \MINUTE_IN_SECONDS; // Cache for 5 minutes
-});
-```
+```php
+add_filter( 'iawmlf_dashboard_link_stats_cache_expiry', function( int $cache_expiry ): int {
+    return 5 * \MINUTE_IN_SECONDS; // Cache for 5 minutes
+});
+```
@@
-```php
-add_filter( 'iawmlf_dashboard_onboarding_stats_cache_expiry', function( int $cache_expiry ): int {
-	return 5 * \MINUTE_IN_SECONDS; // Cache for 5 minutes
-});
-```
+```php
+add_filter( 'iawmlf_dashboard_onboarding_stats_cache_expiry', function( int $cache_expiry ): int {
+    return 5 * \MINUTE_IN_SECONDS; // Cache for 5 minutes
+});
+```
templates/admin/dashboard/widget.php (1)

1-18: Update widget docblock to include onboarding and stats parameters

The template now relies on $iawmlf_onboarding_details and $iawmlf_link_stats, but they aren’t documented in the file header. It’s worth adding them so consumers know the full expected context.

 /**
  * Template for the dashboard widget.
@@
- * @param int    $iawmlf_link_check_duration     Number of days between link checks.
- * @param int    $iawmlf_failed_check_count      Number of failed checks before marking as broken.
+ * @param int    $iawmlf_link_check_duration     Number of days between link checks.
+ * @param int    $iawmlf_failed_check_count      Number of failed checks before marking as broken.
+ * @param array  $iawmlf_onboarding_details      Onboarding details and post counts.
+ * @param array  $iawmlf_link_stats              Link statistics array (e.g. total_links).
  */

Also applies to: 32-43

src/Dashboard/Dashboard_Page.php (1)

73-81: Reuse computed $link_stats instead of calling Dashboard_Statistics::get_link_statistics() twice

The new helpers and the switch to Dashboard_Statistics look good, but in render_page() you already fetch $link_stats at Line 176 and then call Dashboard_Statistics::get_link_statistics() again at Line 249 for iawmlf_link_stats.

To avoid redundant work (and keep behavior consistent if the implementation of get_link_statistics() changes), consider reusing the existing variable:

-		$link_stats = Dashboard_Statistics::get_link_statistics();
+		$link_stats = Dashboard_Statistics::get_link_statistics();
...
-				'iawmlf_link_stats'                 => Dashboard_Statistics::get_link_statistics(),
+				'iawmlf_link_stats'                 => $link_stats,

You might also want to remove STATS_TRANSIENT_KEY if nothing else uses it anymore.

Also applies to: 176-183, 249-269

src/Dashboard/Dashboard_Statistics.php (2)

208-226: Onboarding stats aren’t actually cached; either cache them or drop the transient read

get_onboarding_statistics() reads from ONBOARDING_STATS_TRANSIENT_KEY but never calls set_transient() after compiling fresh stats, so:

  • The first call always falls back to compile_onboarding_statistics() whenever the transient is missing or invalid.
  • Subsequent calls in the same request still bypass caching, since no transient is written.

If you intend onboarding stats to be cached similarly to link stats, you probably want:

 	public static function get_onboarding_statistics(): array {
 		$from_cache = get_transient( self::ONBOARDING_STATS_TRANSIENT_KEY );
@@
-		if ( false === $from_cache || ! is_array( $from_cache ) ) {
-			$stats = self::compile_onboarding_statistics();
-		} else {
+		if ( false === $from_cache || ! is_array( $from_cache ) ) {
+			$stats = self::compile_onboarding_statistics();
+		} else {
@@
-		}
-
-		return $stats;
+		}
+
+		// Optionally cache for reuse; adjust expiry as needed.
+		set_transient( self::ONBOARDING_STATS_TRANSIENT_KEY, $stats, 5 * \MINUTE_IN_SECONDS );
+
+		return $stats;
 	}

Alternatively, if you don’t want caching, you can simplify the method by removing the transient access entirely.

Also applies to: 273-302


273-302: Minor doc/comment mismatch in onboarding compile helper

compile_onboarding_statistics() returns a structured array with show_onboarding set to false when onboarding is over, but the inline comment says:

// If we have no oneboarding date or its 7 days or more ago, return empty array, as onboarding is over.

Since you’re deliberately returning defaulted counts and metadata, consider updating the comment to match the actual behavior (e.g., “return defaults with show_onboarding = false”). This avoids confusion for future readers, but it’s not functionally problematic.

src/Settings/Settings.php (1)

451-558: Wizard and onboarding accessors centralize state cleanly (minor doc tweak suggested)

The new helpers:

  • get_setup_wizard_step() / update_setup_wizard_step()
  • is_wizard_completed() / set_wizard_completed()
  • get_onboarding_date() / set_onboarding_date()
  • set_onboarding_status() / get_onboarding_status()

nicely encapsulate all wizard/onboarding state and add validation for onboarding statuses.

One small polish item: the docblock for get_onboarding_status() says the default is ONBOARDING_PENDING_OPTION, but the parameter default is ONBOARDING_COMPLETED_OPTION:

/**
 * @param string $default_value Optional default value if not set. Default ONBOARDING_PENDING_OPTION.
 */
public static function get_onboarding_status( string $default_value = self::ONBOARDING_COMPLETED_OPTION ): string {

Either the comment or the default should be updated so they agree, to avoid confusion for future callers.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9f1db35 and a865b96.

📒 Files selected for processing (9)
  • README.md (1 hunks)
  • src/Dashboard/Dashboard_Notifications.php (1 hunks)
  • src/Dashboard/Dashboard_Page.php (4 hunks)
  • src/Dashboard/Dashboard_Statistics.php (1 hunks)
  • src/Dashboard/Setup_Wizard.php (10 hunks)
  • src/Settings/Settings.php (4 hunks)
  • templates/admin/dashboard/onboarding.php (1 hunks)
  • templates/admin/dashboard/page.php (2 hunks)
  • templates/admin/dashboard/widget.php (3 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-11-17T23:43:45.310Z
Learnt from: gin0115
Repo: a8cteam51/internet-archive-wayback-machine-link-fixer PR: 250
File: src/Settings/Settings.php:301-311
Timestamp: 2025-11-17T23:43:45.310Z
Learning: In the Internet Archive Wayback Machine Link Fixer plugin (file: src/Settings/Settings.php), the `iawmlf_add_own_content_to_wayback_machine` filter is intentionally applied after the production environment check in the `add_own_links()` method. This prevents the filter from overriding the hard block on archiving in non-production environments (staging, development, local), which is expected behavior to ensure staging/dev sites are never archived.

Applied to files:

  • src/Dashboard/Setup_Wizard.php
🧬 Code graph analysis (6)
src/Dashboard/Dashboard_Page.php (3)
src/Dashboard/Report_Page.php (1)
  • get_page_url (58-60)
src/Dashboard/Settings_Page.php (1)
  • get_page_url (62-64)
src/Dashboard/Dashboard_Statistics.php (3)
  • Dashboard_Statistics (22-339)
  • get_link_statistics (41-61)
  • get_onboarding_statistics (208-226)
src/Dashboard/Dashboard_Notifications.php (1)
src/Dashboard/Dashboard_Statistics.php (3)
  • Dashboard_Statistics (22-339)
  • get_onboarding_statistics (208-226)
  • get_link_statistics (41-61)
src/Dashboard/Dashboard_Statistics.php (3)
src/Link/Link.php (6)
  • Link (21-497)
  • is_broken (180-182)
  • get_id (118-120)
  • has_archived_href (257-259)
  • get_last_check (299-306)
  • get_archive_process (372-374)
src/Settings/Settings.php (3)
  • Settings (22-590)
  • get_onboarding_date (508-511)
  • get_allowed_post_types (101-103)
src/Link/Link_Repository.php (2)
  • Link_Repository (23-673)
  • query_links (394-510)
templates/admin/dashboard/page.php (2)
functions.php (1)
  • iawmlf_render_template (195-230)
src/Dashboard/Report_Page.php (2)
  • Report_Page (23-361)
  • get_page_url (58-60)
templates/admin/dashboard/widget.php (3)
src/Dashboard/Dashboard_Page.php (3)
  • Dashboard_Page (30-294)
  • is_current_page (78-81)
  • get_page_url (88-90)
functions.php (1)
  • iawmlf_render_template (195-230)
src/Dashboard/Report_Page.php (2)
  • Report_Page (23-361)
  • get_page_url (58-60)
src/Dashboard/Setup_Wizard.php (3)
src/Event/Scan_Posts_Event.php (2)
  • Scan_Posts_Event (24-174)
  • force_add_to_action_scheduler (72-90)
src/Settings/Settings.php (10)
  • Settings (22-590)
  • is_wizard_completed (484-486)
  • get_onboarding_status (553-558)
  • update_setup_wizard_step (473-475)
  • set_onboarding_status (535-542)
  • get_onboarding_date (508-511)
  • set_onboarding_date (522-524)
  • should_scan_existing_posts (211-217)
  • set_wizard_completed (497-499)
  • get_setup_wizard_step (460-462)
src/Util/Environmental.php (2)
  • Environmental (20-38)
  • is_production (27-37)
🪛 GitHub Actions: PHP Coding Standards
src/Dashboard/Dashboard_Notifications.php

[warning] 100-100: Array double arrow not aligned correctly; expected 14 space(s) between "'iawmlf_link_stats'" and double arrow, but found 9. (WordPress.Arrays.MultipleStatementAlignment.DoubleArrowNotAligned)


[error] 100-100: There should be a comma after the last array item in a multi-line array. (NormalizedArrays.Arrays.CommaAfterLast.MissingMultiLine)

src/Dashboard/Dashboard_Statistics.php

[error] 211-211: Expected 1 spaces after opening parenthesis; 0 found (PEAR.Functions.FunctionCallSignature.SpaceAfterOpenBracket)


[error] 211-211: Expected 1 spaces before closing parenthesis; 0 found (PEAR.Functions.FunctionCallSignature.SpaceBeforeCloseBracket)

templates/admin/dashboard/widget.php

[warning] 23-23: This comment is 56% valid code; is this commented out code? (Squiz.PHP.CommentedOutCode.Found)

🪛 markdownlint-cli2 (0.18.1)
README.md

817-817: Hard tabs
Column: 1

(MD010, no-hard-tabs)


827-827: Hard tabs
Column: 1

(MD010, no-hard-tabs)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build (ubuntu-latest, 8.4)
🔇 Additional comments (5)
src/Dashboard/Setup_Wizard.php (3)

300-310: Onboarding completion helper is straightforward and consistent

complete_onboarding() centralizes updating the onboarding status and date, and the “only set date if missing” behavior makes repeated calls idempotent.

No issues here; the helper looks solid and keeps the rest of the wizard code cleaner.


317-319: Step‑1 handler is minimal but correct

handle_step_1() now only advances the wizard to step-2 via Settings::update_setup_wizard_step( 'step-2' );. For a purely informational first step, that’s sufficient and keeps logic simple.

No changes needed.


471-503: Settings‑backed wizard state makes step resolution more robust

Switching get_step_data() to use Settings::get_setup_wizard_step() and centralizing step validation and skipping of step-3 for non‑production environments looks good.

The logic for:

  • Normalizing invalid state back to 'step-1',
  • Removing step-3 when not production, and
  • Computing next and progress from the STEPS array

is clear and matches the intended environment-dependent flow.

No changes needed here.

src/Dashboard/Dashboard_Statistics.php (1)

41-61: Link statistics implementation and caching look solid

The link stats pipeline:

  • get_link_statistics() → transient-backed with configurable expiry via iawmlf_dashboard_link_stats_cache_expiry.
  • compile_link_statistics() computes all the necessary counts and recent checks.
  • normalize_link_statistics() ensures the cached payload remains well-formed.

The overall design is good and keeps dashboard rendering lightweight after the first hit. One minor polish point: the docblock for get_link_statistics() doesn’t mention the last_checks field, which is part of the returned array and used elsewhere; updating the annotation would help static analysis but isn’t functionally blocking.

Also applies to: 123-195

src/Settings/Settings.php (1)

29-46: New settings constants for wizard/onboarding are well-structured

The additional option keys (POST_ACTIVATION_ONBOARDING_KEY, SETUP_WIZARD_STEP_KEY, SETUP_WIZARD_COMPLETED_KEY, ONBOARDING_DATE_KEY) and onboarding status constants (ONBOARDING_COMPLETED_OPTION, ONBOARDING_PENDING_OPTION) are named consistently with the existing scheme and keep the settings surface understandable.

No issues here.

Also applies to: 59-62

Comment thread src/Dashboard/Dashboard_Statistics.php
Comment thread templates/admin/dashboard/widget.php

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
src/Event/Scan_Posts_Event.php (1)

72-90: Reuse the stricter Action Scheduler guard for the normal scheduler path as well

The new force_add_to_action_scheduler() correctly checks for as_unschedule_all_actions() and as_enqueue_async_action() before use. add_to_action_scheduler() still only checks as_has_scheduled_action() but later calls as_enqueue_async_action() / as_schedule_single_action(), which could fatal if Action Scheduler is only partially loaded. Consider centralizing a guard that verifies all required functions and reusing it in both methods to keep error handling consistent.

🧹 Nitpick comments (8)
templates/admin/dashboard/widget.php (2)

26-45: Guard against missing onboarding and stats keys in template context

The onboarding block assumes $iawmlf_onboarding_details['show_onboarding'] and $iawmlf_link_stats['total_links'] always exist. If this widget is ever rendered without those keys, PHP notices will be raised. Consider tightening the condition and defaults, e.g.:

<?php if ( ! empty( $iawmlf_onboarding_details['show_onboarding'] )
	&& ! Dashboard_Page::is_current_page() ) : ?>
    …
    'iawmlf_total_links_count' => isset( $iawmlf_link_stats['total_links'] ) ? (int) $iawmlf_link_stats['total_links'] : 0,

7-18: Update template docblock to reflect new data inputs

The docblock lists many $iawmlf_* parameters but omits $iawmlf_onboarding_details and $iawmlf_link_stats, which are now required by the onboarding section. Add these to the param list (with their expected shapes) so callers and static analysis have an accurate contract.

Also applies to: 31-40

src/Dashboard/Setup_Wizard.php (2)

71-83: Align add_onboarding_body_class static-ness with how it’s hooked

add_onboarding_body_class() is declared public static but is registered as an instance callback (array( $this, 'add_onboarding_body_class' )). This works in PHP, but it’s inconsistent and can trip linters. Either drop static on the method or register it as array( __CLASS__, 'add_onboarding_body_class' ) to keep declaration and usage aligned.

Also applies to: 45-47


113-143: Consider reusing Settings capability helper for onboarding redirect

maybe_trigger_onboarding_wizard() currently hard-codes current_user_can( 'manage_options' ). Elsewhere, capabilities are centralized via Settings::get_reporting_page_capability(). If you want integrators to be able to relax/tighten access through one filter, it may be cleaner to use that helper here as well for consistency (unless the wizard is intentionally restricted to manage_options only).

src/Dashboard/Dashboard_Statistics.php (2)

314-341: Optimize get_post_count() query to reduce overhead

get_post_count() runs a WP_Query over all allowed post types without specifying fields/posts_per_page, even though only found_posts is used. To reduce memory and query cost you can set:

$args = array(
    'post_type'              => Settings::get_allowed_post_types(),
    'posts_per_page'         => 1,
    'fields'                 => 'ids',
    'no_found_rows'          => false, // still need found_posts
    'cache_results'          => false,
    'update_post_meta_cache' => false,
    'meta_query'             => $meta_query,
);

This keeps found_posts accurate but minimizes the result set and object hydration.


287-289: Fix typo in onboarding comment

The inline comment says “no oneboarding date”; should be “no onboarding date”. Cheap to fix and keeps the codebase tidy.

src/Settings/Settings.php (2)

553-560: Docblock default for get_onboarding_status() doesn’t match implementation

The docblock says the default is ONBOARDING_PENDING_OPTION, but the method signature defaults to ONBOARDING_COMPLETED_OPTION and falls back to ONBOARDING_PENDING_OPTION only when the stored value is invalid. Adjust the docblock to describe the actual behavior to avoid confusion when reading or generating IDE hints.


569-591: clear_all_options() leaves the onboarding date option behind

clear_all_options() now deletes onboarding-related keys (status, wizard step, completion) but not ONBOARDING_DATE_KEY. Given the method’s purpose is to “Clear all the options”, this leftover value is surprising and could leak stale onboarding metadata after a reset. Consider also deleting self::ONBOARDING_DATE_KEY here.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a865b96 and 08f2488.

📒 Files selected for processing (6)
  • src/Dashboard/Dashboard_Notifications.php (1 hunks)
  • src/Dashboard/Dashboard_Statistics.php (1 hunks)
  • src/Dashboard/Setup_Wizard.php (9 hunks)
  • src/Event/Scan_Posts_Event.php (1 hunks)
  • src/Settings/Settings.php (5 hunks)
  • templates/admin/dashboard/widget.php (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/Dashboard/Dashboard_Notifications.php
🧰 Additional context used
🧬 Code graph analysis (4)
src/Dashboard/Setup_Wizard.php (3)
src/Event/Scan_Posts_Event.php (2)
  • Scan_Posts_Event (24-174)
  • force_add_to_action_scheduler (72-90)
src/Settings/Settings.php (12)
  • Settings (22-592)
  • is_wizard_completed (486-488)
  • get_onboarding_status (555-560)
  • update_setup_wizard_step (475-477)
  • set_onboarding_status (537-544)
  • get_onboarding_date (510-513)
  • set_onboarding_date (524-526)
  • get_fixer_option (273-275)
  • should_scan_existing_posts (211-217)
  • set_wizard_completed (499-501)
  • own_link_routinely_update (353-358)
  • get_setup_wizard_step (462-464)
src/Util/Environmental.php (2)
  • Environmental (20-38)
  • is_production (27-37)
templates/admin/dashboard/widget.php (3)
src/Dashboard/Dashboard_Page.php (3)
  • Dashboard_Page (30-294)
  • is_current_page (78-81)
  • get_page_url (88-90)
functions.php (1)
  • iawmlf_render_template (195-230)
src/Dashboard/Report_Page.php (2)
  • Report_Page (23-361)
  • get_page_url (58-60)
src/Dashboard/Dashboard_Statistics.php (3)
src/Link/Link.php (6)
  • Link (21-497)
  • is_broken (180-182)
  • get_id (118-120)
  • has_archived_href (257-259)
  • get_last_check (299-306)
  • get_archive_process (372-374)
src/Settings/Settings.php (3)
  • Settings (22-592)
  • get_onboarding_date (510-513)
  • get_allowed_post_types (101-103)
src/Link/Link_Repository.php (2)
  • Link_Repository (23-673)
  • query_links (394-510)
src/Event/Scan_Posts_Event.php (1)
src/Settings/Settings.php (2)
  • Settings (22-592)
  • should_scan_existing_posts (211-217)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: build (ubuntu-latest, 8.3)
  • GitHub Check: build (ubuntu-latest, 8.4)
  • GitHub Check: build (ubuntu-latest, 8.1)
  • GitHub Check: build (ubuntu-latest, 8.2)
🔇 Additional comments (1)
templates/admin/dashboard/widget.php (1)

153-159: Dashboard navigation button logic looks correct

Conditionally hiding the Dashboard button when already on the dashboard via Dashboard_Page::is_current_page() avoids redundant links and keeps the nav consistent with the other buttons.

Comment on lines +122 to +193
private static function compile_link_statistics(): array {
$all_links = ( new Link_Repository() )->query_links( \PHP_INT_MAX, 1, array(), array(), array(), Link_Repository::ORDER_DATE_DESC, null, null, null );

// Get all the links stats.
$all_broken = array();
$redirected_broken = array();
$has_archive_link = array();
$not_checked = array();
$process_done = array();
$process_new = array();
$process_pending = array();
$last_checks = array();

// Loop through all links to gather stats.
foreach ( $all_links as $link ) {
if ( $link->is_broken() && ! $link->is_excluded() ) {
$all_broken[] = $link->get_id();
}

if ( $link->is_broken() && $link->has_archived_href() && ! $link->is_excluded() ) {
$redirected_broken[] = $link->get_id();
}

if ( $link->has_archived_href() ) {
$has_archive_link[] = $link->get_id();
}

if ( null === $link->get_last_check() ) {
$not_checked[] = $link->get_id();
} else {
$last = $link->get_last_check();
$last_checks[] = array(
'id' => $link->get_id(),
'last_check' => $last,
);
}

switch ( $link->get_archive_process() ) {
case Link::PROCESS_NEW:
$process_new[] = $link->get_id();
break;
case Link::PROCESS_PENDING:
$process_pending[] = $link->get_id();
break;
default:
$process_done[] = $link->get_id();
break;
}
}

// Sort the last checks by date desc.
usort(
$last_checks,
function ( $a, $b ) {
return strtotime( $b['last_check']['date'] ) <=> strtotime( $a['last_check']['date'] );
}
);

$stats = array(
'total_links' => count( $all_links ),
'all_broken_links' => count( $all_broken ),
'broken_and_redirected_links' => count( $redirected_broken ),
'broken_not_redirected_links' => count( $all_broken ) - count( $redirected_broken ),
'links_with_archive' => count( $has_archive_link ),
'links_without_archive' => count( $all_links ) - count( $has_archive_link ),
'not_checked' => count( $not_checked ),
'process_done' => count( $process_done ),
'process_new' => count( $process_new ),
'process_pending' => count( $process_pending ),
'last_checks' => array_slice( $last_checks, 0, absint( apply_filters( 'iawmlf_dashboard_link_count', 10 ) ) ),
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

Full-table link scan for stats may not scale on large sites

compile_link_statistics() pulls all links via query_links( \PHP_INT_MAX, 1, … ) and iterates them in PHP. On sites with a large iawmlf_link_archive table this will be heavy every time the transient is cold or invalid, and it runs inside admin requests. Consider aggregating via SQL (e.g., dedicated repository methods that return counts and last-check summaries only) or at least paging through the data instead of using PHP_INT_MAX.



---

🏁 Script executed:

```shell
#!/bin/bash
# Find and examine Link_Repository class and its query_links method
rg -n 'class Link_Repository' -A 5 --type=php

Length of output: 392


🏁 Script executed:

#!/bin/bash
# Find query_links method implementation
rg -n 'function query_links|public.*query_links' -A 15 --type=php

Length of output: 3289


🏁 Script executed:

#!/bin/bash
# Find get_link_statistics and compile_link_statistics to see caching logic
rg -n 'function get_link_statistics|function compile_link_statistics' -B 2 -A 20 --type=php

Length of output: 3558


🏁 Script executed:

#!/bin/bash
# Examine Link model methods referenced in the snippet
rg -n '(is_broken|is_excluded|has_archived_href|get_archive_process)' --type=php -B 2 -A 5 | head -100

Length of output: 8469



---

🏁 Script executed:

```shell
#!/bin/bash
# Get full query_links implementation
sed -n '394,500p' src/Link/Link_Repository.php

Length of output: 4173


🏁 Script executed:

#!/bin/bash
# Find Link model class and its method implementations
rg -n 'class Link' -A 3 --type=php | grep -A 3 'src/Link/Link.php'

Length of output: 320


🏁 Script executed:

#!/bin/bash
# Check Link model methods: is_broken, is_excluded, has_archived_href
rg -n 'function is_broken|function is_excluded|function has_archived_href|function get_archive_process' -A 8 --type=php src/Link/Link.php

Length of output: 807


🏁 Script executed:

#!/bin/bash
# Find all calls to get_link_statistics to understand usage context
rg -n 'get_link_statistics' --type=php -B 2 -A 2

Length of output: 1613


Performance concern is valid — full-table link scan in compile_link_statistics() will not scale

The method fetches all links with query_links( \PHP_INT_MAX, 1, … ), which executes a single SQL query with no LIMIT/OFFSET constraint and loads every link as a Link object into memory. On sites with large iawmlf_link_archive tables, this causes:

  • Single massive database query (all rows fetched at once)
  • High memory usage from object hydration
  • Full PHP loop iteration on every cache miss

Since the transient expires every 2 minutes and this runs on dashboard page load, cache-cold scenarios will trigger expensive recalculation repeatedly.

Recommendation: Implement SQL-side aggregation (e.g., repository methods returning COUNT, GROUP BY results) instead of fetching and looping all rows in PHP. Alternatively, page through data in chunks and aggregate within the loop to keep memory bounded.

@gin0115 gin0115 merged commit c3f821c into trunk Nov 21, 2025
11 checks passed
@gin0115 gin0115 linked an issue Nov 21, 2025 that may be closed by this pull request
@coderabbitai coderabbitai Bot mentioned this pull request Nov 25, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

WP Dashboard widget

1 participant