Skip to content

Remove loading message randomization #3295

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

Merged

Conversation

alecgeatches
Copy link
Contributor

@alecgeatches alecgeatches commented Apr 25, 2025

Description

A small follow-up to #3292 to remove all loading message randomization. The loading messages displayed in the right-hand of the Traffic Boost pane were still randomized.

@alecgeatches alecgeatches self-assigned this Apr 25, 2025
@alecgeatches alecgeatches requested a review from a team as a code owner April 25, 2025 20:51
@alecgeatches alecgeatches changed the title Remove/loading message randomization Remove loading message randomization Apr 25, 2025
Copy link
Contributor

coderabbitai bot commented Apr 25, 2025

📝 Walkthrough

Walkthrough

This update introduces the Traffic Boost feature, significantly expanding both backend and frontend functionality. It adds new REST API endpoints, models, and validation classes to support inbound smart links and traffic boost suggestions, including placement and acceptance workflows. The Smart Link and Inbound Smart Link models are refactored for richer metadata, status management, and ITM parameter handling. On the frontend, a comprehensive React-based dashboard page is introduced, featuring client-side routing, a posts table, and a dedicated Traffic Boost UI with sidebar, preview, and link management components. Supporting code includes new utility functions, provider classes, Redux stores, and extensive SCSS styling. The plugin settings, permissions, and admin menu structure are updated to accommodate the new feature.

Changes

Files/Groups Change Summary
.husky/pre-commit Increased PHPStan memory limit from 1G to 2G.
composer.json Added masterminds/html5 as a dependency.
package.json Added routing, cookie, and debounce dependencies and type definitions.
phpstan.neon Lowered PHPStan type coverage thresholds.
src/@types/assets/window.d.ts Added optional _parsely_traffic_boost_preview_nonce to Window.
src/Models/class-inbound-smart-link.php
src/Models/class-smart-link.php
src/Models/class-smart-link-status.php
Major enhancements: new constants, properties, methods for traffic boost, status handling, ITM URL management, robust HTML parsing, placement validation, and querying.
src/Telemetry/telemetry-init.php Updated action hook for settings page telemetry.
src/UI/class-dashboard-page.php Introduced dashboard page class for admin UI, script/style enqueueing, AJAX preview, and data injection.
src/UI/class-recommended-widget.php
src/UI/class-site-health.php
src/content-helper/common/class-content-helper-feature.php
Updated settings page URL references.
src/UI/class-row-actions.php Added "Traffic Boost" link to post/page row actions with permission and status checks.
src/UI/class-settings-page.php Added "Traffic Boost" feature to settings, updated submenu registration, and improved asset enqueueing.
src/UI/settings-page.php Added settings_errors() call to display settings feedback.
src/Utils/class-utils.php Added ITM parameter appending utility and type alias.
src/class-parsely.php Added canonical URL management methods, updated menu slugs, and integrated traffic boost feature.
src/class-permissions.php Added "TrafficBoost" to permissions logic.
src/content-helper/common/base-provider.tsx JSDoc punctuation updates.
src/content-helper/common/base-wordpress-provider.tsx Added page fetching, improved error handling, and type enhancements.
src/content-helper/common/components/loading/* Added new Loading component with typewriter effect, exports, and styles.
src/content-helper/common/components/snackbar-notices/* Added SnackbarNotices component, exports, and styles.
src/content-helper/common/components/thumbnail/component.tsx Enhanced accessibility and lazy loading for images.
src/content-helper/common/components/vertical-divider/component.tsx Made divider height configurable via prop.
src/content-helper/common/content-helper-error.tsx Added TrafficBoost-specific error code and made error code public.
src/content-helper/common/css/common.scss Imported new snackbar and loading styles.
src/content-helper/common/css/variables.scss Added dashboard and traffic boost variables, new base units, and selector adjustments.
src/content-helper/common/icons/error-icon.tsx Added new ErrorIcon component.
src/content-helper/common/settings/types/* Added TrafficBoostSettings type and included in union.
src/content-helper/common/utils/functions.ts Added ITM parameter add/remove utility functions.
src/content-helper/common/utils/permissions.ts Added and reordered TrafficBoost permission.
src/content-helper/dashboard-page/components/* Added shared dashboard components: containers, headers, posts table, and typography.
src/content-helper/dashboard-page/dashboard-page.* Added main dashboard React component with routing and styles.
src/content-helper/dashboard-page/pages/* Added dashboard, traffic boost, and settings page components and styles.
src/content-helper/dashboard-page/pages/traffic-boost/* Added full Traffic Boost UI: preview, sidebar, provider, Redux store, link management, and all supporting components and styles.
src/content-helper/dashboard-page/provider.tsx Added DashboardProvider singleton for API interactions.
src/content-helper/editor-sidebar/smart-linking/* Refactored smart link types to support ITM URLs, status, and context; updated validation and processing logic.
src/css/admin-settings.scss Renamed main settings page CSS class selector.
src/js/admin-settings.ts Added Traffic Boost feature checkbox to event handler logic.
src/rest-api/class-base-validation.php Added abstract base class for REST API parameter validation.
src/rest-api/content-helper/class-content-helper-controller.php Registered new Traffic Boost API endpoint.
src/rest-api/content-helper/class-endpoint-smart-linking.php Enhanced smart link endpoint: status, context, ITM href array, validation, and batch operations.
src/rest-api/content-helper/class-endpoint-traffic-boost.php Added new REST endpoint for traffic boost suggestions, placements, acceptance, updates, and deletions.
src/rest-api/content-helper/validations/* Added blending weight and URL exclusion list validation/sanitization classes.
src/rest-api/settings/class-endpoint-traffic-boost-settings.php
src/rest-api/settings/class-settings-controller.php
Added Traffic Boost settings endpoint and registered it.
src/rest-api/stats/class-endpoint-posts.php Added URL validation for REST API parameter.
src/services/class-base-service-endpoint.php Set request timeout to 60 seconds.
src/services/suggestions-api/class-suggestions-api-service.php Added inbound links and positions endpoints, refactored method signatures, and improved API interaction.
src/services/suggestions-api/endpoints/* Added inbound links and positions endpoints, refactored linked reference endpoint for options and exclusion list, updated timeout for API requests.
tests/Integration/PermissionsTest.php Added Traffic Boost to tested features.
tests/Integration/RestAPI/ContentHelper/ContentHelperControllerTest.php Updated test for new endpoint registration.
tests/Integration/RestAPI/ContentHelper/EndpointSmartLinkingTest.php Updated tests for new href structure and tracking parameters.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant DashboardPage (React)
    participant TrafficBoostSidebar (React)
    participant TrafficBoostProvider (JS)
    participant REST_API
    participant Inbound_Smart_Link (PHP)
    participant WP_Post

    User->>DashboardPage: Navigates to Traffic Boost page
    DashboardPage->>TrafficBoostSidebar: Renders sidebar, requests suggestions/inbound links
    TrafficBoostSidebar->>TrafficBoostProvider: getInboundLinks(postId)
    TrafficBoostProvider->>REST_API: GET /traffic-boost/inbound-links
    REST_API->>Inbound_Smart_Link: Query inbound smart links for post
    Inbound_Smart_Link->>WP_Post: Load post data
    REST_API-->>TrafficBoostProvider: Return inbound links
    TrafficBoostProvider-->>TrafficBoostSidebar: Return inbound links
    TrafficBoostSidebar-->>DashboardPage: Display inbound links

    User->>TrafficBoostSidebar: Clicks "Generate Suggestions"
    TrafficBoostSidebar->>TrafficBoostProvider: generateBatchSuggestions(postId, n)
    TrafficBoostProvider->>REST_API: POST /traffic-boost/suggestions
    REST_API->>Inbound_Smart_Link: Generate suggestions via Suggestions API
    Inbound_Smart_Link->>WP_Post: Validate placement, create smart links
    REST_API-->>TrafficBoostProvider: Return suggestions
    TrafficBoostProvider-->>TrafficBoostSidebar: Update sidebar with new suggestions

    User->>DashboardPage: Selects a suggestion
    DashboardPage->>TrafficBoostProvider: acceptSuggestion(postId, suggestionId)
    TrafficBoostProvider->>REST_API: POST /traffic-boost/accept
    REST_API->>Inbound_Smart_Link: Apply smart link to post content
    REST_API-->>TrafficBoostProvider: Return status
    TrafficBoostProvider-->>DashboardPage: Update UI with applied link
Loading

Possibly related PRs

  • Parsely/wp-parsely#3149: Adds the masterminds/html5 dependency and backend support for Traffic Boost, directly related to backend changes here.
  • Parsely/wp-parsely#2939: Implements permissions infrastructure for Traffic Boost, directly connected to permissions and settings changes in this PR.
  • Parsely/wp-parsely#2553: Adds and extends the Inbound_Smart_Link model, which is further enhanced and integrated in this PR.

Suggested labels

Maintenance

Suggested reviewers

  • LauraKalnicky
✨ Finishing Touches
  • 📝 Generate Docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@alecgeatches alecgeatches changed the base branch from develop to add/traffic-boost April 25, 2025 20:52
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Caution

Inline review comments failed to post. This is likely due to GitHub's limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

🛑 Comments failed to post (45)
phpstan.neon (1)

16-17: 💡 Verification agent

🧩 Analysis chain

Confirm if type coverage threshold reduction is necessary.

The return type coverage threshold has been lowered from 90% to 88%, and parameter type coverage from 79.2% to 79.1%. While these are small reductions, consider whether this is a temporary accommodation or if additional type improvements should be planned for future PRs.


🏁 Script executed:

#!/bin/bash
# Check current type coverage to see if it matches the adjusted thresholds

# Run PHPStan with type coverage output
vendor/bin/phpstan analyse --memory-limit=2G --configuration=phpstan.neon --no-progress --error-format=json | jq '.meta.type_coverage'

Length of output: 203


🏁 Script executed:

#!/bin/bash
# Verify installed PHPStan version to confirm support for type_coverage thresholds
vendor/bin/phpstan --version

Length of output: 73


Fix phpstan.neon type coverage settings

It looks like PHPStan 1.12.24 doesn’t recognize the type_coverage parameters, so your thresholds aren’t being applied. Please correct the config (or install/enable the Type Coverage extension) before adjusting the numbers again.

• File: phpstan.neon (around lines 16–17)
– Remove or rename the invalid type_coverage block.
– Use the supported format (e.g. under parameters: typeCoverage: returnType: 88, paramType: 79.1) or add the necessary extension.
• Once the configuration is valid, re-evaluate whether lowering return-type coverage from 90%→88% and param-type coverage from 79.2%→79.1% is required, or if you can plan additional type improvements in upcoming PRs.

src/content-helper/editor-sidebar/smart-linking/component.tsx (1)

377-377: ⚠️ Potential issue

Updated to access the raw URL property in SmartLink object.

The property access was updated to use link.href.raw instead of link.href to align with changes to the SmartLink type, where the href property was changed from a string to an object containing raw and itm URL properties.

However, there are two related issues that should be addressed:

  1. At line 379, the console.warn statement still uses link.href directly, which may print "[object Object]" instead of the URL.
  2. At line 388, the comparison sl.href === link.href will not work correctly since link.href is now an object.

Apply this diff to fix these issues:

- console.warn( `PCH Smart Linking: Skipping self-reference link: ${ link.href }` );
+ console.warn( `PCH Smart Linking: Skipping self-reference link: ${ link.href.raw }` );

- if ( sl.href === link.href ) {
+ if ( sl.href.raw === link.href.raw ) {
📝 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.

			if ( link.href.raw.includes( strippedPermalink ) ) {
-				console.warn( `PCH Smart Linking: Skipping self-reference link: ${ link.href }` );
+				console.warn( `PCH Smart Linking: Skipping self-reference link: ${ link.href.raw }` );
			}

			// … other logic …

-			if ( sl.href === link.href ) {
+			if ( sl.href.raw === link.href.raw ) {
				// …
			}
src/content-helper/common/settings/types/traffic-boost-settings.d.ts (1)

1-8: 🛠️ Refactor suggestion

Interface seems to use a placeholder property.

The TrafficBoostSettings interface is defined with a single property named Setting1 of type string. This appears to be a placeholder rather than a meaningful setting name for the Traffic Boost feature.

Consider renaming this property to something more descriptive that accurately represents an actual Traffic Boost setting (e.g., enabledFeatures, boostLimit, etc.) or add the actual properties that will be needed for Traffic Boost settings.

export interface TrafficBoostSettings {
-	Setting1: string;
+	// Consider replacing with actual settings properties
+	enabledFeatures: string[];
+	// Add other relevant properties as needed
}
📝 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.

/**
 * Defines the Traffic Boost settings structure.
 *
 * @since 3.18.0
 */
export interface TrafficBoostSettings {
	// Consider replacing with actual settings properties
	enabledFeatures: string[];
	// Add other relevant properties as needed
}
package.json (3)

81-81: ⚠️ Potential issue

Duplicate dependency declaration.

The lodash.debounce package is already included in dependencies at line 42. This duplicate declaration should be removed to avoid potential version conflicts.

-		"lodash.debounce": "^4.0.8",
📝 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.



49-49: ⚠️ Potential issue

Potential duplicate dependency.

The @types/js-cookie is already added to dependencies at line 39. Having the same package in both dependencies and devDependencies is redundant and could lead to version conflicts.

-		"@types/js-cookie": "^3.0.6",
📝 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.



51-51: 💡 Verification agent

🧩 Analysis chain

Type definitions match React Router version?

Ensure the @types/react-router version is compatible with the react-router version at line 83. The React Router v7 might need different type definitions than what's currently specified.


🌐 Web query:

What version of @types/react-router is compatible with react-router v7?

💡 Result:

There is no separate @types/react-router package required or available for React Router v7. Starting with React Router v6, and continuing with v7, all type definitions are included directly within the main react-router package itself. The old @types/react-router package on DefinitelyTyped is not maintained for v6 or later, and is incompatible with v7[1][9].

What to use with React Router v7:

  • Simply install react-router@latest (or the v7 version you need); TypeScript types are bundled.
  • Do not install @types/react-router with v7—it is obsolete for v7 and will lead to type conflicts[1][9].

Summary Table

React Router Version Type Definitions Source
v5 and earlier @types/react-router
v6 and v7 Bundled with react-router

If you're upgrading from an older version and see @types/react-router in your dependencies, you should remove it when moving to v7. This is the recommended, type-safe approach for both JavaScript and TypeScript projects with modern React Router[4][9].

Citations:


Remove obsolete @types/react-router dependency

For React Router v7, all TypeScript definitions are bundled with the main react-router package. The separate @types/react-router package only supports v5 and earlier and will conflict with v7. Please remove it from your dependencies:

  • package.json (line 51): delete
  • "@types/react-router": "^5.1.20",



<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **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.

`````suggestion
{
"dependencies": {
-  "@types/react-router": "^5.1.20",
 // …other dependencies…
},
// …rest of package.json…
}
src/content-helper/dashboard-page/pages/traffic-boost/sidebar/components/post-details.tsx (1)

31-46: ⚠️ Potential issue

Security concern with dangerouslySetInnerHTML.

While the component structure is clean and the loading state is handled appropriately, there's a security concern with using dangerouslySetInnerHTML for the post title.

-					<div className="post-title" dangerouslySetInnerHTML={ { __html: post?.title.rendered ?? __( '(no title)', 'wp-parsely' ) } } />
+					<div className="post-title">
+						{ post?.title.rendered 
+							? <span dangerouslySetInnerHTML={ { __html: post.title.rendered } } />
+							: __( '(no title)', 'wp-parsely' )
+						}
+					</div>

This change ensures that dangerouslySetInnerHTML is only used when necessary (for rendered HTML titles) and not for the fallback text, which should be treated as plain text.

📝 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.

return (
	<div className="traffic-boost-post-details">
		<div className="traffic-boost-post-details-label">
			{ __( 'Current Post:', 'wp-parsely' ) }
		</div>
		<div className="traffic-boost-post-details-content">
			<Thumbnail
				post={ post }
				size={ 52 }
				className="traffic-boost-thumbnail"
			/>
			{ isLoading && <Spinner /> }
			{ ! isLoading && (
				<div className="post-title">
					{ post?.title.rendered
						? <span dangerouslySetInnerHTML={ { __html: post.title.rendered } } />
						: __( '(no title)', 'wp-parsely' )
					}
				</div>
			) }
		</div>
	</div>
);
🧰 Tools
🪛 Biome (1.9.4)

[error] 44-44: Avoid passing content using the dangerouslySetInnerHTML prop.

Setting content using code can expose users to cross-site scripting (XSS) attacks

(lint/security/noDangerouslySetInnerHtml)

src/content-helper/dashboard-page/components/posts-table/style.scss (3)

78-79: 🛠️ Refactor suggestion

Hardcoded color values should use variables per SCSS coding guidelines.

Instead of using the hardcoded color #757575, use a corresponding color variable from the variables.scss file.

-		color: #757575;
+		color: var(--gray-500);

152-153: 🛠️ Refactor suggestion

Padding uses hardcoded pixel value instead of variables.

The padding contains a hardcoded value of 2px that should be converted to a variable or to_rem function for consistency.

-				padding: 2px var(--grid-unit-10);
+				padding: var(--grid-unit-02) var(--grid-unit-10);

If the specific variable doesn't exist, you should convert it to rem units:

-				padding: 2px var(--grid-unit-10);
+				padding: to_rem(2) var(--grid-unit-10);
📝 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.

				border-radius: to_rem(6px);
-				padding: 2px var(--grid-unit-10);
+				padding: var(--grid-unit-02) var(--grid-unit-10);

52-52: 🛠️ Refactor suggestion

Fixed pixel values should use to_rem function per coding guidelines.

The width: to_rem(180px) syntax might cause issues. The to_rem function typically expects a numeric value without the 'px' unit.

-				width: to_rem(180px);
+				width: to_rem(180);
📝 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.

				width: to_rem(180);
src/content-helper/dashboard-page/pages/traffic-boost/sidebar/sidebar.tsx (3)

66-96: 🛠️ Refactor suggestion

Consider using React state instead of direct DOM manipulation.

Using direct DOM manipulation with document.querySelector and manually updating content isn't the React way and can lead to issues with the Virtual DOM reconciliation.

Consider refactoring to use React state and JSX to handle the tab counter display:

// Example approach (simplified)
const [suggestionCount, setSuggestionCount] = useState(0);
const [inboundCount, setInboundCount] = useState(0);

useEffect(() => {
  setSuggestionCount(suggestions.length);
  setInboundCount(inboundLinks.length);
}, [suggestions.length, inboundLinks.length]);

// Then in your JSX:
<>
  {suggestionCount > 0 && <span className="tab-count">{suggestionCount}</span>}
</>

This would be more in line with React's declarative approach.


96-96: ⚠️ Potential issue

Fix dependency array in useEffect.

The useEffect dependency array includes inboundLinks twice: once as the object and once as its length. This is redundant and could cause unnecessary re-renders.

- }, [ inboundLinks, inboundLinks.length, suggestions.length ] );
+ }, [ inboundLinks.length, suggestions.length ] );
📝 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.

    }, [ inboundLinks.length, suggestions.length ] );

103-108: 🛠️ Refactor suggestion

Avoid simulating DOM clicks in React.

Simulating clicks with tab.click() isn't recommended in React. Instead of manipulating the DOM directly, consider updating your TabPanel implementation to accept and use the selectedTab state:

<TabPanel
  className="traffic-boost-sidebar-tabs"
+ selected={selectedTab}
  tabs={ [
    // ...
  ] }
  onSelect={ ( tab: string ) => setSelectedTab( tab as TrafficBoostSidebarTabs ) }
>

If the WordPress TabPanel component doesn't support this, consider wrapping it with your own component that does.

Committable suggestion skipped: line range outside the PR's diff.

src/content-helper/dashboard-page/pages/traffic-boost/preview/components/preview-header.tsx (1)

167-176: 🛠️ Refactor suggestion

Remove dead code – condition always short-circuits.

&& false && guarantees the “Toggle Frontend Preview” button never appears. If the intention is to keep the feature behind a flag, extract that flag to a prop or constant; otherwise, delete the block to simplify rendering.

- { ! isExternalURL( activeLink ) && false && (
+ { ! isExternalURL( activeLink ) && (
   <Button
     …
   />
 ) }
📝 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.

				{ ! isExternalURL( activeLink ) && (
					<Button
						icon={ desktop }
						isPressed={ isFrontendPreview }
						disabled={ isLoading }
						iconSize={ 24 }
						onClick={ onToggleFrontendPreview }
						label={ __( 'Toggle Frontend Preview', 'wp-parsely' ) }
					/>
				) }
src/content-helper/dashboard-page/pages/traffic-boost/sidebar/components/links-list/links-list.tsx (1)

317-333: ⚠️ Potential issue

Page selector never fires due to inverted guard.

handlePageChange exits early when ! useScrollbar, yet the <select> is only rendered when ! useScrollbar is true. Consequently, manual page selection is broken.

-	const handlePageChange = ( value?: string ) => {
-		if ( ! useScrollbar ) {
-			return;
-		}
+	const handlePageChange = ( value?: string ) => {
+		if ( useScrollbar ) {
+			return; // Page selector is disabled when scrollbar mode is active.
+		}

Verify navigation after patching.

📝 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.

	const handlePageChange = ( value?: string ) => {
		if ( useScrollbar ) {
			return; // Page selector is disabled when scrollbar mode is active.
		}

		if ( ! value ) {
			return;
		}

		let selectedPage = parseInt( value, 10 );
		if ( selectedPage > totalPages ) {
			selectedPage = totalPages;
		} else if ( selectedPage < 1 ) {
			selectedPage = 1;
		}
		onPageChange?.( selectedPage );
	};
src/content-helper/dashboard-page/components/posts-table/component.tsx (1)

198-204: 🛠️ Refactor suggestion

itemsPerPage freezes to its initial value when query.per_page changes.

useState initialiser runs only once, so if the parent component updates query.per_page the table will still use the old itemsPerPage. Replace the state with useMemo (or derive directly) and keep a single source of truth.

-const [ itemsPerPage ] = useState<number>( query.per_page ?? 10 );
+const itemsPerPage = query.per_page ?? 10;
📝 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.

	const [ currentPage, setCurrentPage ] = useState<number>( 1 );
	const [ totalPages, setTotalPages ] = useState<number>( 1 );
-	const [ itemsPerPage ] = useState<number>( query.per_page ?? 10 );
+	const itemsPerPage = query.per_page ?? 10;
	const [ isLoading, setIsLoading ] = useState<boolean>( true );
	const didFirstSearch = useRef( false );
src/content-helper/dashboard-page/pages/traffic-boost/preview/components/preview-iframe.tsx (2)

140-169: ⚠️ Potential issue

Event listeners added on every load without corresponding removal risk memory leaks.

disableNavigation attaches several capturing listeners (click, submit, contextmenu) each time the iframe reloads. Over time this accumulates. Keep references so you can removeEventListener during iframe cleanup or wrap them in once: true.


74-83: 🛠️ Refactor suggestion

crypto.randomUUID() is not available in all supported browsers.

Older Safari (≤ 14.1) and IE do not implement randomUUID. Provide a small fallback (e.g. UUID v4 polyfill) to avoid breaking previews on legacy browsers.

src/services/suggestions-api/endpoints/class-endpoint-suggest-inbound-links.php (1)

63-71: 🛠️ Refactor suggestion

Sanitisation: use sanitize_text_field() for titles, not esc_attr().

esc_attr() is appropriate when outputting to an HTML attribute, but here the value is stored/processed server-side and later returned in JSON. Using sanitize_text_field() avoids HTML-escaping quotes and keeps the title human-readable.

-esc_attr( $link['title'] ),
+sanitize_text_field( $link['title'] ),

Committable suggestion skipped: line range outside the PR's diff.

src/content-helper/dashboard-page/pages/traffic-boost/single-post-component.tsx (3)

270-274: 🛠️ Refactor suggestion

Avoid mutating React state objects in place.

link.smartLink.is_link_replacement = true; mutates an object that is very likely referenced in Redux/React state, bypassing change detection. Instead, clone the object and call the relevant store action.

-        if ( didReplaceLink ) {
-            link.smartLink.is_link_replacement = true;
-        }
+        if ( didReplaceLink ) {
+            updateInboundLink(
+                { ...link, smartLink: { ...link.smartLink, is_link_replacement: true } },
+                link.smartLink.uid
+            );
+        }
📝 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.

        if ( didReplaceLink ) {
            updateInboundLink(
                { ...link, smartLink: { ...link.smartLink, is_link_replacement: true } },
                link.smartLink.uid
            );
        }

        link.targetPost.content.raw = postContent;

120-128: ⚠️ Potential issue

Null-check wpWrap in cleanup to avoid runtime error.

If the initial selector fails (#wpwrap not found), ( wpWrap as HTMLElement ).style.backgroundColor = '' will throw on unmount. Guard the assignment.

-            ( wpWrap as HTMLElement ).style.backgroundColor = '';
+            if ( wpWrap ) {
+                ( wpWrap as HTMLElement ).style.backgroundColor = '';
+            }
📝 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.

		return () => {
			// When the component unmounts, make sure to cancel all the provider requests.
			TrafficBoostProvider.getInstance().cancelAll();
			// Disconnect the resize observer.
			resizeObserver.disconnect();
			// Remove the background color of #wpwrap.
			if ( wpWrap ) {
				( wpWrap as HTMLElement ).style.backgroundColor = '';
			}

			// Clean up the store state.

385-468: 🛠️ Refactor suggestion

fetchSuggestions effect misses key deps — may generate duplicates.

The effect depends on inboundLinks, settings.maxItems, and several dispatcher setters, but the dependency array only contains postId. This can cause:

  1. Suggestions to be generated before inboundLinks load, ignoring the exclusion list.
  2. Settings changes (e.g., different maxItems) not being respected.

Add the missing dependencies or refactor the logic to a callback that is safe to memoise.

-}, [ postId ] ); // eslint-disable-line react-hooks/exhaustive-deps
+}, [ postId, inboundLinks, settings.maxItems ] ); // keep other setters stable via useDispatch
📝 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.

-}, [ postId ] ); // eslint-disable-line react-hooks/exhaustive-deps
+}, [ postId, inboundLinks, settings.maxItems ] ); // eslint-disable-line react-hooks/exhaustive-deps
src/content-helper/dashboard-page/pages/traffic-boost/store.ts (2)

767-799: 🛠️ Refactor suggestion

Duplicate selectors cause confusion and dead weight.

isLoadingSuggestions() and isSuggestionsLoading() both expose the same logic (loading.includes('suggestions')). Please consolidate into a single selector to keep the public store surface minimal and prevent future divergence.

Also applies to: 797-800


401-412: ⚠️ Potential issue

Avoid mutating the incoming Redux action object.

The reducer mutates action.loadingType when it is falsy. Redux reducers should be pure functions and never mutate their inputs (actions or state). This can also violate TypeScript’s readonly expectations for action objects created by actions.setLoading().

-			case 'SET_LOADING':
-				if ( ! action.loadingType ) {
-					action.loadingType = 'default';
-				}
-				return {
+			case 'SET_LOADING': {
+				const loadingType = action.loadingType ?? 'default';
+				return {
 					...state,
-					loading: action.isLoading
-						? [ ...state.loading, action.loadingType ]
-						: state.loading.filter( ( type ) => type !== action.loadingType ),
+					loading: action.isLoading
+						? [ ...state.loading, loadingType ]
+						: state.loading.filter( ( type ) => type !== loadingType ),
 				};
+			}
📝 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.

			case 'SET_LOADING': {
				const loadingType = action.loadingType ?? 'default';
				return {
					...state,
					loading: action.isLoading
						? [ ...state.loading, loadingType ]
						: state.loading.filter( ( type ) => type !== loadingType ),
				};
			}
			case 'SET_ERROR':
src/services/suggestions-api/endpoints/class-endpoint-suggest-inbound-link-positions.php (3)

70-78: 🛠️ Refactor suggestion

Gracefully handle missing canonical URLs.

Parsely::get_canonical_url_from_post() returns the literal string “no permalink” on failure. Forwarding that value will produce unusable suggestions. Return WP_Error early if either canonical URL is invalid.


85-88: 🛠️ Refactor suggestion

Validate performance blending weight range.

performance_blending_weight is forwarded verbatim; values outside 0-1 will be rejected upstream and surface as hard-to-diagnose 400 errors. Add a clamp or validation before building $request_body.


109-128: ⚠️ Potential issue

URLs should be sanitised before instantiation.

$link['target_url'] and $link['source_url'] are consumed directly. Although data is remote-origin, always escape with esc_url_raw() when persisting or further processing.

-				$link['target_url'],
+				esc_url_raw( $link['target_url'] ),
...
-			$smart_link->set_source_post( $source_post, $link['source_url'] );
+			$smart_link->set_source_post( $source_post, esc_url_raw( $link['source_url'] ) );
📝 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.

		$suggestions = array();
		/** @var LinkPositionResponse $link */
		$link = $response[0];

		foreach ( $link['anchor_texts'] as $anchor_text_suggestion ) {
			$smart_link = new Inbound_Smart_Link(
				esc_url_raw( $link['target_url'] ),
				esc_attr( $link['title'] ),
				wp_kses_post( $anchor_text_suggestion['text'] ),
				$anchor_text_suggestion['offset']
			);

			// Set the source post and update the canonical URL.
			$smart_link->set_source_post( $source_post, esc_url_raw( $link['source_url'] ) );

			// Set the destination post.
			$smart_link->set_destination_post( $destination_post );

			$suggestions[] = $smart_link;
		}
src/content-helper/common/base-wordpress-provider.tsx (2)

258-262: 🛠️ Refactor suggestion

rest_endpoint leaks into query string.

You construct the endpoint with addQueryArgs() after merging the full queryParams object, so rest_endpoint itself becomes a query parameter sent to /wp/v2/posts. Strip it out first:

-		const restEndpoint = queryParams.rest_endpoint ?? '/wp/v2/posts';
-		const posts = await this.apiFetch<Post[]>( {
-			path: addQueryArgs( restEndpoint, { ...queryParams, _embed: true, context: 'edit' } ),
+		const { rest_endpoint: restEndpoint = '/wp/v2/posts', ...cleanParams } = queryParams;
+		const posts = await this.apiFetch<Post[]>( {
+			path: addQueryArgs( restEndpoint, { ...cleanParams, _embed: true, context: 'edit' } ),
📝 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.

		// Extract and rename/rest_endpoint, defaulting to '/wp/v2/posts', and strip it from the query params.
		const { rest_endpoint: restEndpoint = '/wp/v2/posts', ...cleanParams } = queryParams;

		const posts = await this.apiFetch<Post[]>( {
			path: addQueryArgs( restEndpoint, { ...cleanParams, _embed: true, context: 'edit' } ),
			method: 'GET',

162-164: ⚠️ Potential issue

wpError.json() is not guaranteed – can throw a secondary error.

@wordpress/api-fetch rejects with a plain Error whose shape differs from Response. Calling json() on it crashes the handler. Guard with an instanceof Response check and fall back to the normal WPErrorObject.

-			const errorData = await wpError.json();
-			return Promise.reject( new ContentHelperError( errorData.message ?? '', errorData.code, '' ) );
+			if ( wpError instanceof Response ) {
+				const errorData = await wpError.json();
+				return Promise.reject(
+					new ContentHelperError( errorData.message ?? '', errorData.code ?? ContentHelperErrorCode.UnknownError, '' ),
+				);
+			}
+			// Fallback: re-wrap the original error.
+			return Promise.reject(
+				new ContentHelperError( wpError.message ?? '', ContentHelperErrorCode.UnknownError, '' ),
+			);
📝 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.

			if ( wpError instanceof Response ) {
				const errorData = await wpError.json();
				return Promise.reject(
					new ContentHelperError(
						errorData.message ?? '',
						errorData.code ?? ContentHelperErrorCode.UnknownError,
						'',
					),
				);
			}
			// Fallback: re-wrap the original error.
			return Promise.reject(
				new ContentHelperError(
					wpError.message ?? '',
					ContentHelperErrorCode.UnknownError,
					'',
				),
			);
		} finally {
src/content-helper/dashboard-page/pages/traffic-boost/sidebar/components/tabs/suggestions-tab.tsx (1)

198-240: 🛠️ Refactor suggestion

⚠️ Potential issue

Risk of irreversible list clearing on generation failure.

handleGenerateSuggestions wipes the current suggestions state before the async generateBatchSuggestions call succeeds. If the provider throws early, users permanently lose their existing list and must regenerate. A safer pattern is:

- // Clear the suggestions list.
- setSuggestions( [] );
-
- await trafficBoostProvider.generateBatchSuggestions( … );
+ const previousSuggestions = suggestions;          // snapshot
+ setSuggestions( [] );
+
+ await trafficBoostProvider.generateBatchSuggestions( … );
+ // restore if nothing was generated
+ if ( ! select( TrafficBoostStore ).getSuggestions().length ) {
+     setSuggestions( previousSuggestions );
+ }

Alternatively, defer the clear until the first onNewSuggestions callback.

Committable suggestion skipped: line range outside the PR's diff.

src/content-helper/editor-sidebar/smart-linking/utils.ts (2)

436-439: 🛠️ Refactor suggestion

Possible double-encoding of ITM parameters.

addSmartLinkITMParamsToURL( link.href, uid ) is invoked after the raw URL is extracted with removeITMParamsFromURL, but it re-uses the original link.href which might already contain ITM params. While URLSearchParams.set overrides duplicates, redundant work causes noisy diffs and hampers caching. Prefer:

- itm: addSmartLinkITMParamsToURL( link.href, uid ),
+ itm: addSmartLinkITMParamsToURL( href.raw, uid ),

so we always start from a clean URL.

📝 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.

		const href = {
			raw: removeITMParamsFromURL( link.href ),
-			itm: addSmartLinkITMParamsToURL( link.href, uid ),
+			itm: addSmartLinkITMParamsToURL( href.raw, uid ),
		};

599-603: ⚠️ Potential issue

URL comparison is too fuzzy; may restore wrong link.

a.href.includes( missingSmartLink.href.raw ) matches on substrings, so https://example.com/foo will also match https://example.com/foobar. Use strict origin/path equality to avoid restoring metadata to an unrelated link:

- a.href.includes( missingSmartLink.href.raw )
+ new URL( a.href ).href === new URL( missingSmartLink.href.raw ).href

(Be sure to wrap in try/catch for malformed URLs.)

📝 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.

		const link = Array.from( blockDoc.querySelectorAll( 'a' ) ).find( ( a ) => {
			return a.textContent === missingSmartLink.text &&
				new URL( a.href ).href === new URL( missingSmartLink.href.raw ).href &&
				! a.hasAttribute( 'data-smartlink' );
		} );
src/content-helper/dashboard-page/pages/traffic-boost/preview/hooks/use-iframe-highlight.ts (2)

588-603: ⚠️ Potential issue

Domain check mis-classifies protocol-relative & sub-domain links.

link.href.includes( siteUrl ) will treat https://blog.example.com as internal for example.com, and may mis-label protocol-relative (//example.com). Resolve both hosts and compare:

const isSameHost = ( url: string, referenceHost: string ): boolean => {
	try {
		return new URL( url ).hostname === referenceHost;
	} catch {
		return false;
	}
};


links = links.filter( ( link ) => isSameHost( link.href, siteUrl ) );

This aligns with WordPress’ approach to “internal link” detection.


52-60: 🛠️ Refactor suggestion

Style element injected on every render – leads to duplicates & CSS bloat.

injectHighlightStyles appends a <style> tag each time it runs. Navigating between posts or toggling the preview quickly piles up identical rules. Before insertion, probe iframeDocument.head for an existing node with an id/class, e.g.:

- const style = iframeDocument.createElement( 'style' );
+ if ( iframeDocument.getElementById( 'pch-highlight-styles' ) ) {
+     return;
+ }
+ const style = iframeDocument.createElement( 'style' );
+ style.id = 'pch-highlight-styles';

This keeps the DOM lean and avoids redundant CSS parsing.

📝 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.

const injectHighlightStyles = useCallback( ( iframe: HTMLIFrameElement ) => {
    const iframeDocument = iframe.contentDocument ?? iframe.contentWindow?.document;
    if ( ! iframeDocument ) {
        return;
    }

    // Avoid injecting duplicate styles.
    if ( iframeDocument.getElementById( 'pch-highlight-styles' ) ) {
        return;
    }

    const style = iframeDocument.createElement( 'style' );
    style.id = 'pch-highlight-styles';
    style.textContent = `
        /** Smart link highlight styles. */
        /* …rest of CSS… */
    `;
    iframeDocument.head.appendChild( style );
}, [] );
src/content-helper/dashboard-page/pages/traffic-boost/preview/preview.tsx (2)

291-299: 🛠️ Refactor suggestion

Avoid mutating Redux / selector objects in place.

link.isSuggestion = false; mutates the object that was previously selected from the store.
When using Redux (or any shared-state library) mutating in place breaks referential equality, making downstream selectors, middleware and React memoisation unreliable.

Instead create a shallow copy (or better, dispatch a dedicated action):

-// Flag isSuggestion to false.
-link.isSuggestion = false;
-// Add the link to the inbound links list.
-addInboundLink( link );
+const updatedLink = { ...link, isSuggestion: false };
+addInboundLink( updatedLink );
📝 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.

		// Remove suggestion from the list.
		removeSuggestion( link );

		const updatedLink = { ...link, isSuggestion: false };
		addInboundLink( updatedLink );

149-172: 🛠️ Refactor suggestion

useEffect dependency list can trigger an unnecessary re-render loop.

previewUrl is included in the dependency array but is also updated inside the effect.
If activePost or isFrontendPreview remain unchanged, the effect will still run twice: once to set the new URL, and immediately again because previewUrl has changed – even though the computed newUrl is identical. This costs an extra paint of the iframe.

A safer pattern is to derive newUrl in a useMemo (or simply remove previewUrl from the dependency list) and compare with the current state before calling setPreviewUrl.

-}, [ activePost, isFrontendPreview, previewUrl ] ); // …
+}, [ activePost, isFrontendPreview ] );

Make sure to keep the “only update when changed” early-return guard so users don’t lose the loading spinner.

src/UI/class-dashboard-page.php (1)

169-175: 🛠️ Refactor suggestion

Menu slug contains a hash – WordPress may strip this.

add_submenu_page() uses 'parsely-dashboard-page#/traffic-boost' as the slug.
Core sanitisation removes the # fragment, so the submenu can’t be matched later, breaking highlight and permissions checks.

Prefer adding the submenu with the canonical slug (parsely-dashboard-page) and letting the React router handle the hash internally.

src/content-helper/dashboard-page/pages/traffic-boost/provider.ts (2)

227-271: ⚠️ Potential issue

Retry loop may starve when no new suggestions are returned.

maxRetries is only decremented when newGeneratedSuggestions.length === 0 or the catch fires.
If the API repeatedly returns duplicate suggestions (filtered out by urlExclusionList), filteredGeneratedSuggestions.length can be 0, yet newGeneratedSuggestions.length >0. In that case the loop spins forever.

Consider decrementing maxRetries when no additions were made:

- if ( newGeneratedSuggestions.length === 0 ) {
+ if ( filteredGeneratedSuggestions.length === 0 ) {
     maxRetries--;
     continue;
 }

This prevents an infinite loop while still allowing the designated retry budget.

Committable suggestion skipped: line range outside the PR's diff.


579-603: 💡 Verification agent

🧩 Analysis chain

Guard against undefined post IDs sent to getPosts().

postIds may contain undefined values when inboundSmartLink.source?.post_id is missing (e.g. links to external URLs). Passing such values to this.getPosts() causes needless round-trips and—in some back-ends—400 responses.

- const postIds = smartLinks.map( ( inboundSmartLink ) => inboundSmartLink.source?.post_id );
+ const postIds = smartLinks
+   .map( ( inboundSmartLink ) => inboundSmartLink.source?.post_id )
+   .filter( ( id ): id is number => typeof id === 'number' && id > 0 );

The same filter should be applied earlier in getExistingSuggestions() when you compute postIds for the early-return check.


🏁 Script executed:

#!/bin/bash
# Find the definition of InboundSmartLink to inspect the source.post_id type
rg -n "InboundSmartLink" -C3

# Locate getExistingSuggestions to see how postIds are computed there
rg -n "getExistingSuggestions" -C5 src/content-helper/dashboard-page

# Confirm occurrences of .post_id in the codebase
rg -n "post_id" -C3 src

Length of output: 66808


🏁 Script executed:

#!/bin/bash
# Show definitions of SmartLink and InboundSmartLink
rg -n "export type SmartLink" -C10 src/content-helper/editor-sidebar/smart-linking/provider.ts
rg -n "export type InboundSmartLink" -C10 src/content-helper/editor-sidebar/smart-linking/provider.ts

# Show property 'source' in that file
rg -n "source" -C5 src/content-helper/editor-sidebar/smart-linking/provider.ts

Length of output: 2728


🏁 Script executed:

#!/bin/bash
# Display the LinkedPost type definition to verify its fields
rg -n "type LinkedPost" -A20 src/content-helper/editor-sidebar/smart-linking/provider.ts

Length of output: 573


🏁 Script executed:

#!/bin/bash
# Display the full implementation of getExistingSuggestions
sed -n '160,240p' src/content-helper/dashboard-page/pages/traffic-boost/provider.ts

Length of output: 3111


🏁 Script executed:

#!/bin/bash
# Show the definition of BaseWordPressProvider and getPosts method
rg -n "class BaseWordPressProvider" -C20 src/common/base-wordpress-provider.ts

Length of output: 158


Guard against undefined post IDs to prevent invalid getPosts() calls
Map and then filter inboundSmartLink.source?.post_id so only valid numbers are sent to the WordPress API. Apply this both when you build postIds for getPosts() and in your early-return in getExistingSuggestions().

Examples:

--- a/src/content-helper/dashboard-page/pages/traffic-boost/provider.ts
+++ b/src/content-helper/dashboard-page/pages/traffic-boost/provider.ts
@@ private async createTrafficBoostLinks( inboundSmartLinks: InboundSmartLink[] ): Promise<TrafficBoostLink[]> {
-   const postIds = smartLinks.map( ( inboundSmartLink ) => inboundSmartLink.source?.post_id );
+   const postIds = smartLinks
+     .map( ( inboundSmartLink ) => inboundSmartLink.source?.post_id )
+     .filter( ( id ): id is number => typeof id === 'number' && id > 0 );
 
@@ public async getExistingSuggestions( postId: number ): Promise<TrafficBoostLink[]> {
-   const postIds = response.data.map( ( inboundSmartLink ) => inboundSmartLink.source?.post_id );
-   if ( postIds.length === 0 ) {
+   const postIds = response.data
+     .map( ( inboundSmartLink ) => inboundSmartLink.source?.post_id )
+     .filter( ( id ): id is number => typeof id === 'number' && id > 0 );
+   if ( postIds.length === 0 ) {
     return [];
   }
📝 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.

 		const getPostsPromises = Object.entries( smartLinksByPostType ).map( async ( [ postRestEndpoint, smartLinks ] ) => {
 			// Get the post IDs from the inbound smart links.
-			const postIds = smartLinks.map( ( inboundSmartLink ) => inboundSmartLink.source?.post_id );
+			const postIds = smartLinks
+				.map( ( inboundSmartLink ) => inboundSmartLink.source?.post_id )
+				.filter( ( id ): id is number => typeof id === 'number' && id > 0 );
 
 			// Fetch the posts for the inbound smart links.
 			const fetchedPosts = await this.getPosts( {
 				include: postIds,
 				posts_per_page: 100,
src/rest-api/content-helper/class-endpoint-traffic-boost.php (2)

143-148: 🛠️ Refactor suggestion

ignore_keywords lacks validation / sanitisation.

Other array parameters (keyword_exclusion_list, url_exclusion_list) rely on dedicated validation classes, but ignore_keywords is taken at face value. Malformed data could trigger PHP warnings or bypass expectations further downstream.

Add callbacks similar to the other parameters:

- 'ignore_keywords'             => array(
-     'type'        => 'array',
-     'description' => __( 'The keywords to ignore.', 'wp-parsely' ),
-     'required'    => false,
- ),
+ 'ignore_keywords'             => array(
+     'type'              => 'array',
+     'description'       => __( 'The keywords to ignore.', 'wp-parsely' ),
+     'required'          => false,
+     'validate_callback' => 'wp_parse_list',   // or a dedicated Validate_Keyword_List class
+     'sanitize_callback' => 'wp_kses_post',    // or similar lightweight sanitiser
+ ),

495-501: ⚠️ Potential issue

in_array() call risks a warning when $keyword_exclusion_list is null.

The variable is nullable ($request->get_param() returns null when the parameter is absent).
Passing null to in_array() triggers a warning in strict-types mode.

- if ( in_array( $suggestion->text, $keyword_exclusion_list, true ) ) {
+ if ( ! empty( $keyword_exclusion_list )
+      && in_array( $suggestion->text, (array) $keyword_exclusion_list, true ) ) {
     continue;
 }

This keeps the logic intact while ensuring safe execution.

📝 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.

		foreach ( $suggestions as $suggestion ) {
			// If the ignore keywords are set and the suggested text is in the ignore keywords, skip it.
			if ( ! empty( $keyword_exclusion_list )
			     && in_array( $suggestion->text, (array) $keyword_exclusion_list, true ) ) {
				continue;
			}

			/** @var WP_Error|bool $valid_placement Whether the suggestion has a valid placement. */
src/Models/class-smart-link.php (2)

1042-1044: 🛠️ Refactor suggestion

Invalid orderby value 'date modified' in WP_Query.

WP_Query accepts 'modified' or 'modified_gmt', not 'date modified'.
The current string falls back to the default order and logs a notice when WP_DEBUG is enabled.

- 'orderby' => 'date modified',
+ 'orderby' => 'modified',

Adjust to guarantee predictable ordering and suppress unnecessary log noise.

📝 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.

				'orderby' => 'modified',
				'order'   => 'ASC',
			),

1092-1095: ⚠️ Potential issue

Non-static method invoked statically – breaks under strict_types.

flush_cache() calls self::get_cache_group(), but get_cache_group() is an instance method.
PHP 8 emits a deprecation notice (and future versions a fatal error) for static invocation of non-static methods.

- wp_cache_delete( 'smart-link-uid-map-' . $this->uid . '-' . $this->source_post_id, self::get_cache_group() );
+ wp_cache_delete( 'smart-link-uid-map-' . $this->uid . '-' . $this->source_post_id, $this->get_cache_group() );
📝 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.

	protected function flush_cache(): void {
		// Delete the cache for the smart link UID to post ID association.
		wp_cache_delete( 'smart-link-uid-map-' . $this->uid . '-' . $this->source_post_id, $this->get_cache_group() );
	}
src/Models/class-inbound-smart-link.php (2)

514-540: ⚠️ Potential issue

find_text_node() offset tracking is broken due to pass-by-value.
$offset is decremented inside the function, but the recursive calls receive an unchanged copy, so only the first branch ever reaches zero. The nth occurrence can therefore never be found.

-private function find_text_node( \DOMNode $node, string $search_text, int $offset ) {
+private function find_text_node( \DOMNode $node, string $search_text, int &$offset ) {-        foreach ( $node->childNodes as $child ) {
-            $result = $this->find_text_node( $child, $search_text, $offset );
+        foreach ( $node->childNodes as $child ) {
+            $result = $this->find_text_node( $child, $search_text, $offset );
             if ( null !== $result ) {
                 return $result;
             }
         }

Passing $offset by reference allows the counter to propagate through the traversal, correctly locating the targeted occurrence.

📝 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.

	private function find_text_node( \DOMNode $node, string $search_text, int &$offset ) {
		/* phpcs:disable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase */
		// If the node is a text node, check if it contains the search text.
		if ( XML_TEXT_NODE === $node->nodeType ) {
			if ( strpos( $node->textContent, $search_text ) !== false ) {
				if ( 0 === $offset ) {
					return $node;
				}
				--$offset;
			}
		}

		// If the node has child nodes, recursively search for the text node.
		if ( $node->hasChildNodes() ) {
			/** @var \DOMNode $child */
			foreach ( $node->childNodes as $child ) {
				$result = $this->find_text_node( $child, $search_text, $offset );
				if ( null !== $result ) {
					return $result;
				}
			}
		}

		// If the text node is not found, return null.
		return null;
		/* phpcs:enable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase */
	}

788-792: 🛠️ Refactor suggestion

Line-level str_replace() risks unintended multiple replacements.
str_replace( $original_line, $line_with_smart_link, $source_post_content ) will replace every identical line in the post, not just the one inside the target paragraph. This can corrupt content if the same HTML appears elsewhere.

Prefer a position-based replacement using the DOM or substr_replace() with exact byte offsets, or scope the replacement to the current paragraph string you already have:

-$source_post_content = str_replace( $original_line, $line_with_smart_link, $source_post_content );
+$source_post_content = str_replace(
+    $original_paragraph_html,          // original paragraph
+    $html_parser->saveHTML( $paragraph_fragment ), // updated paragraph
+    $source_post_content
+);

This confines the change to the known paragraph and avoids collateral edits.

📝 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.

		// Replace the original line with the new HTML.
		$source_post_content = str_replace(
			$original_paragraph_html,          // original paragraph
			$html_parser->saveHTML( $paragraph_fragment ), // updated paragraph
			$source_post_content
		);

		// Backup the original post content and paragraph in a post meta, for rollback purposes.

@alecgeatches alecgeatches requested a review from acicovic April 28, 2025 19:50
@acicovic acicovic added this to the 3.19.0 milestone May 6, 2025
@alecgeatches alecgeatches merged commit 99c1087 into add/traffic-boost May 7, 2025
42 checks passed
@alecgeatches alecgeatches deleted the remove/loading-message-randomization branch May 7, 2025 16:46
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.

2 participants