Feat/user directory free pro functionality#1778
Feat/user directory free pro functionality#1778arifulhoque7 wants to merge 25 commits intoweDevsOfficial:developfrom
Conversation
Implements a limited User Directory in the free version, including setup wizard UI, REST API, shortcode handler, and directory limit logic. Adds new assets, React components, admin menu, post type registration, and views for directory/profile layouts. Pro-only features are previewed with disabled states and upgrade prompts, following the Pro Preview pattern.
Added ! prefix to purple color classes in layout helpers and Tailwind config to ensure higher specificity. Updated safelist in Tailwind config to include both prefixed and non-prefixed classes, and adjusted button class usage for consistency.
Improves member count calculation in the REST API and displays live member counts in the directory list. Refines input styles for number fields, updates the toast notification component for better UX, and adds a Pro badge to the 'New Directory' button when the directory limit is reached.
Introduces a new 'Files' tab to the user profile with grouped file display and filtering, excluding private message attachments. Updates Gruntfile.js to add user directory build/watch tasks and ensures profile size is passed to the template. Also adds the corresponding template file for the new tab.
Introduced a wide range of new utility classes to form-builder.css for layout, spacing, sizing, display, border, background, and other style properties. These additions improve flexibility and consistency for admin UI components and support more granular styling options.
Moved user directory assets and configs to modules/user-directory, updated Gruntfile.js to reflect new paths, and adjusted build/watch tasks. Renamed Tailwind and Webpack configs, added PostCSS config, and updated package files to support the new module structure.
Updated sort option values from 'ID' to 'id' and adjusted related logic for consistency. Removed free/pro checks from handlers to simplify state updates. Improved UI/UX for avatar and sort options, added Pro-locked features with badges and tooltips, and enhanced descriptions for better clarity.
Changed primary color scheme from emerald to purple across helpers and Tailwind config. Updated profile layout-2 to add a cover photo section, adjust class names, and improve header overlap. Cleaned up the About tab by removing Pro upgrade prompts and related UI elements.
Changed Tailwind primary and hover colors from purple to emerald green in tailwind.config.js. Updated related CSS class in App.js to use emerald color for upgrade link, ensuring visual consistency with the new color scheme.
Updated avatar rendering to use the same logic as the directory listing, prioritizing custom profile photos, then Gravatar, and finally user initials as a fallback. Improved initials calculation and font sizing for better visual consistency. Simplified markup to show either the avatar image or initials, not both, and ensured consistent sizing and styling.
Add early return if there are no items to display in the pagination shortcode. Also update the current page styling to use layout-specific color classes for better theme consistency.
Refactored multiple files in the user-directory module, including Admin_Menu.php, Directory.php, Helpers.php, Post_Type.php, PrettyUrls.php, Shortcode.php, and profile layout. Updated Toast.js in the frontend and regenerated several minified CSS files to reflect style changes.
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
WalkthroughAdds a Free User Directory: admin React CRUD app, REST endpoints (CRUD, search, user_count), shortcode rendering and templates, pretty‑URL rewrites, helper utilities, free-module registry and AJAX toggle, frontend search/pagination, and module-specific build tooling. Changes
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 14
Note
Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.
🟡 Minor comments (17)
src/js/user-directory/components/common/Tooltip.js-3-6 (1)
3-6: Unstable tooltip ID regenerates on every render.
tooltipIdis generated inline and will change on each re-render, causing unnecessary DOM updates and potential accessibility issues. Also,substr()is deprecated.🔎 Proposed fix using useMemo
-import { useState } from '@wordpress/element'; +import { useState, useMemo } from '@wordpress/element'; const Tooltip = ( { content, children, className = '' } ) => { const [visible, setVisible] = useState(false); - const tooltipId = `wpuf-tooltip-${Math.random().toString(36).substr(2, 9)}`; + const tooltipId = useMemo( + () => `wpuf-tooltip-${Math.random().toString(36).substring(2, 11)}`, + [] + );Committable suggestion skipped: line range outside the PR's diff.
modules/user-directory/package.json-8-24 (1)
8-24: Pin dependency versions for reproducible builds.Using caret (
^) ranges can lead to different versions being installed over time. While the Tailwind version here (^3.4.17) differs from the root config (3.3.5), this is not a breaking change—Tailwind 3.4 is a backward-compatible minor release. However, consider pinning versions to ensure consistent builds across environments and prevent drift from non-deterministic installations.src/js/user-directory/components/DirectoryList.js-22-321 (1)
22-321: Add error handling to clipboard operations and complete i18n coverage
- Line 29–33:
navigator.clipboard.writeText()lacks error handling. Unhandled promise rejections will break the copy flow silently. Add.catch()or wrap in try/catch to handle failures gracefully (e.g., display a fallback message).- Line 200, 214, 219:
"Loading…","Copied!", and"Copy shortcode"are user-visible but missing__()wrapping. Wrap each in__('string', 'wp-user-frontend')to match the component's i18n pattern.- Line 53: Guard
directories.forEach()withif (!Array.isArray(directories)) return;to prevent runtime errors if the prop is null or undefined during initial load.src/js/user-directory/components/common/DeleteConfirmModal.js-49-58 (1)
49-58: Translate button text for internationalization.Lines 50 and 57 have hard-coded English text for the "Cancel" and "Delete" buttons. These should be wrapped with the
__()function for consistency with the rest of the component and to support translations.🔎 Suggested fix
<button onClick={onCancel} className="wpuf-w-[101px] wpuf-h-[50px] wpuf-rounded-md wpuf-border wpuf-border-gray-300 wpuf-bg-white wpuf-text-gray-700 wpuf-font-medium hover:wpuf-bg-gray-50 wpuf-pt-[13px] wpuf-pb-[13px] wpuf-pl-[25px] wpuf-pr-[23px] wpuf-text-[16px] wpuf-leading-[24px]"> - Cancel + {__( 'Cancel', 'wp-user-frontend' )} </button> <button onClick={onConfirm} className="wpuf-w-[151px] wpuf-h-[50px] wpuf-rounded-md wpuf-bg-[#EF4444] wpuf-text-white wpuf-font-medium wpuf-shadow-sm hover:wpuf-bg-red-600 wpuf-pt-[13px] wpuf-pb-[13px] wpuf-pl-[25px] wpuf-pr-[25px] wpuf-text-[16px] wpuf-leading-[24px]" style={{ boxShadow: '0px 1px 2px 0px rgba(0, 0, 0, 0.05)' }}> - Delete + {__( 'Delete', 'wp-user-frontend' )} </button>modules/user-directory/views/profile/template-parts/posts-2.php-65-66 (1)
65-66: Escape permalink URL.
get_permalink()output should be escaped withesc_url().🔎 Proposed fix
- <a href="<?php echo get_permalink(); ?>" + <a href="<?php echo esc_url( get_permalink() ); ?>"modules/user-directory/views/profile/template-parts/posts-2.php-54-57 (1)
54-57: Post title output needs escaping.
wp_trim_words()returns an unescaped string that should be escaped before output.🔎 Proposed fix
<div class="!wpuf-text-base !wpuf-font-normal !wpuf-text-gray-900 !wpuf-leading-none"> - <?php echo wp_trim_words( get_the_title(), 10 ); ?> + <?php echo esc_html( wp_trim_words( get_the_title(), 10 ) ); ?> </div>modules/user-directory/views/directory/template-parts/row-3.php-63-69 (1)
63-69: Incorrect escaping function for mailto link.
esc_url()is not appropriate for email addresses inmailto:links. Useesc_attr()instead, asesc_url()may mangle the email format.🔎 Proposed fix
<?php if ( $user_email ) : ?> <p class="wpuf-text-[14px] wpuf-font-normal wpuf-text-gray-600 !wpuf-m-0"> - <a href="mailto:<?php echo esc_url( $user_email ); ?>" target="_blank" rel="noopener" class="wpuf-text-gray-500 hover:wpuf-text-gray-900"> + <a href="mailto:<?php echo esc_attr( $user_email ); ?>" target="_blank" rel="noopener" class="wpuf-text-gray-500 hover:wpuf-text-gray-900"> <?php echo esc_html( $user_email ); ?> </a> </p> <?php endif; ?>modules/user-directory/views/profile/template-parts/user-avatar.php-6-6 (1)
6-6: Inconsistent version annotation.The docblock states
@since 4.2.0, but based on the PR summary and other files, this module is being introduced in version 4.3.0.🔎 Proposed fix
- * @since 4.2.0 + * @since 4.3.0src/js/user-directory/components/DirectoryWizard.js-145-147 (1)
145-147:handleSaveAndExitdoesn't prevent double submission.If the user clicks "Save and Exit" while already saving,
handleSubmitwill be called again. Theloadingstate is set insidehandleSubmit, not before the await.🔎 Proposed fix
const handleSaveAndExit = async () => { + if (loading) return; await handleSubmit(); };src/js/user-directory/components/steps/StepBasics.js-67-81 (1)
67-81: Clean up debounce timeout on unmount.The debounce timeout stored in
searchTimeoutRefis not cleared when the component unmounts, which could cause a state update on an unmounted component.🔎 Proposed fix — add cleanup effect
+ // Cleanup debounce timeout on unmount + useEffect(() => { + return () => { + if (searchTimeoutRef.current) { + clearTimeout(searchTimeoutRef.current); + } + }; + }, []); + // Close dropdown when clicking outside useEffect(() => {assets/js/ud-search-shortcode.js-194-200 (1)
194-200: Hardcoded English strings are not translatable.The "No users found" messages are hardcoded in JavaScript. Use the localized strings from
wpufUserDirectorySearchor provide fallbacks.🔎 Example approach
const heading = document.createElement('h3'); heading.style.cssText = 'font-size: 1rem; font-weight: 600; color: #111827; margin-bottom: 0.5rem;'; -heading.textContent = 'No users found matching your search criteria.'; +heading.textContent = (typeof wpufUserDirectorySearch !== 'undefined' && wpufUserDirectorySearch.i18n && wpufUserDirectorySearch.i18n.noUsersFound) + ? wpufUserDirectorySearch.i18n.noUsersFound + : 'No users found matching your search criteria.';src/js/user-directory/components/steps/StepAdvanced.js-9-12 (1)
9-12: Inconsistency: Free avatar sizes don't match Admin_Menu.php.The
freeAvatarSizesarray only includes'192', butAdmin_Menu.php(lines 403-412) defines both192and128as free (is_free: true). This inconsistency could cause confusion where users see 128×128 as available in the backend but locked in the wizard.🔎 Proposed fix
- const freeAvatarSizes = ['192']; // Only 192 is free + const freeAvatarSizes = ['128', '192']; // 128 and 192 are free per Admin_Menu.phpsrc/js/user-directory/components/steps/StepAdvanced.js-134-142 (1)
134-142: Typo: Avatar size '265' should be '256'.The avatar sizes array includes
'265'which appears to be a typo. TheAdmin_Menu.phpfile (line 429-432) defines'256'as an avatar size option.🔎 Proposed fix
{ value: '160', label: '160×160', isFree: false }, { value: '192', label: '192×192', isFree: true }, - { value: '265', label: '265×265', isFree: false }, + { value: '256', label: '256×256', isFree: false }, ];modules/user-directory/views/profile/layout-2.php-88-91 (1)
88-91: Unescaped HTML output for contact icons.The
$contact_item['icon']is output without escaping viaecho. While this HTML is generated internally inHelpers.php, it bypasses WordPress escaping functions. Consider usingwp_kses_post()or ensure the icon HTML is sanitized at the source.Proposed fix
<div class="!wpuf-relative"> - <?php echo $contact_item['icon']; ?> + <?php echo wp_kses_post( $contact_item['icon'] ); ?> </div>modules/user-directory/Api/Directory.php-638-640 (1)
638-640: Profile tabs array lacks deep sanitization.The
profile_tabsarray is stored directly without recursive sanitization. Malicious input could persist if the array contains nested values.Proposed fix
// Profile tabs (complex array) if ( isset( $params['profile_tabs'] ) && is_array( $params['profile_tabs'] ) ) { - $settings['profile_tabs'] = $params['profile_tabs']; + $settings['profile_tabs'] = map_deep( $params['profile_tabs'], 'sanitize_text_field' ); }modules/user-directory/DirectoryStyles.php-333-334 (1)
333-334: Sanitize$_SERVER['REQUEST_URI']before use.Direct access to
$_SERVER['REQUEST_URI']should be sanitized.Proposed fix
- $current_uri = trim( $_SERVER['REQUEST_URI'], '/' ); + // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized + $current_uri = isset( $_SERVER['REQUEST_URI'] ) ? trim( sanitize_text_field( wp_unslash( $_SERVER['REQUEST_URI'] ) ), '/' ) : '';modules/user-directory/Shortcode.php-493-505 (1)
493-505: Remove unusedget_current_page()method.Static analysis correctly identifies this private method as unused. It appears to be dead code that should be removed to improve maintainability.
Proposed fix
- /** - * Get current page number (matching Pro logic) - * - * @since 4.3.0 - * - * @return int - */ - private function get_current_page() { - $current_page = 1; - - // Support both ?page=2 and /page/2/ - if ( get_query_var( 'paged' ) ) { - $current_page = (int) get_query_var( 'paged' ); - } elseif ( ! empty( $_GET['page'] ) ) { - // phpcs:ignore WordPress.Security.NonceVerification.Recommended - $current_page = (int) $_GET['page']; - } - - return max( 1, $current_page ); - }
🧹 Nitpick comments (53)
tailwind.config.js (1)
17-19: Redundant content path.The path
'./modules/user-directory/views/**/*.php'is already covered by'./modules/user-directory/**/*.php'. Consider removing the redundant entry.🔎 Proposed fix
// Free User Directory module templates './modules/user-directory/**/*.php', - './modules/user-directory/views/**/*.php',assets/js/admin/wpuf-module.js (1)
25-62: Consider adding defensive checks for the global object.The handler assumes
wpuf_free_modulesis always defined. If the script runs before localization or in an unexpected context, this could throw an error.🔎 Proposed defensive check
// Handle free module toggle $( '.wpuf-toggle-free-module' ).on( 'change', function () { + if ( typeof wpuf_free_modules === 'undefined' ) { + console.error( 'WPUF Free modules configuration not loaded' ); + return; + } + var $toggle = $( this ); var moduleId = $toggle.data( 'module' );src/js/user-directory/components/common/Header.js (1)
10-11: Potentially unused React import.With
react-jsx-runtimein dependencies (per the asset file), the explicitReactimport may be unnecessary. OnlyuseEffectis used.🔎 Proposed fix
-import React, { useEffect } from 'react'; +import { useEffect } from '@wordpress/element';modules/user-directory/views/profile/template-parts/file-2.php (2)
231-241: Unescaped SVG output.
$display_iconand$type_icons[$type]are echoed without escaping. While these are hardcoded SVGs, it's inconsistent with the escaping discipline elsewhere in the template.Since these are trusted hardcoded values, use
wp_kses_post()or explicitly mark as safe:-<div class="!wpuf-text-2xl !wpuf-mb-1"><?php echo $display_icon; ?></div> +<div class="!wpuf-text-2xl !wpuf-mb-1"><?php echo wp_kses_post( $display_icon ); ?></div>
281-316: Consider moving inline JavaScript to an enqueued script.Inline JS for tab switching could be moved to the module's frontend JS file for better separation of concerns and caching benefits.
src/js/user-directory/components/steps/StepProfile.js (1)
4-157: Consider centralizing Pro badge and asset URL logicThe
StepProfileoverlay in Lines 99–107 duplicates the Pro badge image URL resolution pattern used in other components (e.g.,App.js,StepAdvanced.js). Extracting a sharedProBadgehelper (or reusing the existing one) would reduce drift if asset URL logic or badge styling changes, and keep Free/Pro branding consistent across the wizard.src/js/user-directory/styles/main.css (1)
196-302: Reduce duplication and narrow.wpuf-overflow-hiddenoverride if possible
.wpuf-z-\[9999\]and.wpuf-overflow-hiddenare defined twice (Lines 197–204 and 295–302). Keeping only one definition for each will simplify maintenance.- Overriding the Tailwind-style
.wpuf-overflow-hiddenutility globally tooverflow: visible !importantchanges the semantic meaning of that class everywhere on this admin page. If any other component relies on real hidden overflow, this could cause unexpected visual glitches. Where feasible, consider scoping the override to a more specific container (e.g., a table wrapper class) rather than the generic utility.modules/user-directory/postcss.config.js (1)
1-6: PostCSS config is correct and minimalConfig wiring for
tailwindcssandautoprefixeris standard and should work fine with the module-specific build. If other modules use an identical pattern, consider a shared base PostCSS config to reduce duplication, but not required for correctness here.src/js/user-directory/components/common/LayoutCard.js (1)
14-95: Localize the "PRO" badge label for consistency with component patternsThe badge text on line 63 is hardcoded as
PROwhile other user-facing strings in the component use the localization patterni18n?.property || __('Fallback', 'wp-user-frontend')(lines 71, 80). For consistency and to support translations, consider wrapping it:i18n?.pro_label || __('Pro', 'wp-user-frontend').modules/user-directory/views/directory/template-parts/sort-field.php (2)
14-14: Consider adding a prefix to uniqid() for better uniqueness.Line 14 uses
uniqid()without a prefix to generate the block ID. While collisions are unlikely, adding a prefix improves uniqueness and makes IDs more identifiable in the DOM.🔎 Suggested improvement
-$block_id = isset( $block_id ) ? $block_id : 'wpuf-directory-' . uniqid(); +$block_id = isset( $block_id ) ? $block_id : 'wpuf-directory-' . uniqid('sort-', true);
31-47: Consider extracting common select styling to reduce duplication.The dropdown styling (including the inline SVG background) is duplicated across both select elements. Consider defining a shared CSS class or variable to improve maintainability.
src/js/user-directory/components/common/Toast.js (1)
12-12: Consider adding prop validation for the type parameter.The
typeprop defaults to'success'but accepts any string. Invalid types will fall back to undefined styles. Consider adding PropTypes or validating the type to prevent silent failures when an invalid type is passed.🔎 Suggested validation
const Toast = ({ message, type = 'success', duration = 3000, onClose }) => { + const validTypes = ['success', 'error', 'warning', 'info']; + const safeType = validTypes.includes(type) ? type : 'success'; const [isVisible, setIsVisible] = useState(true); const [isLeaving, setIsLeaving] = useState(false);Then use
safeTypeinstead oftypeintypeStyles[type]andicons[type].modules/user-directory/tailwind.config.js (1)
14-75: Review the safelist for necessary entries only.The safelist contains 60+ utility classes, which increases the CSS bundle size. Ensure all these classes are actually needed and cannot be detected through content scanning. Classes that appear dynamically in PHP or JS should remain in the safelist, but hardcoded classes in templates should be automatically detected.
modules/user-directory/views/directory/template-parts/social-icons.php (1)
33-71: Consider adding aria-labels for improved accessibility.The social media links lack descriptive aria-labels, which would help screen reader users identify the platform. Consider adding
aria-labelattributes like"Visit Facebook profile"to each anchor tag.🔎 Example for Facebook link
-<a href="<?php echo esc_url( $facebook_url ); ?>" target="_blank" rel="noopener" class="wpuf-social-icon"> +<a href="<?php echo esc_url( $facebook_url ); ?>" target="_blank" rel="noopener" class="wpuf-social-icon" aria-label="<?php esc_attr_e( 'Visit Facebook profile', 'wp-user-frontend' ); ?>">Apply similar changes to Twitter, LinkedIn, and Instagram links.
src/js/user-directory/components/common/DeleteConfirmModal.js (1)
14-61: Consider adding modal accessibility enhancements.The modal could benefit from the following accessibility improvements:
- Add ARIA attributes:
role="dialog",aria-modal="true",aria-labelledbypointing to the title- Implement keyboard support: ESC key to close, focus trap to prevent tabbing outside the modal
- Consider allowing overlay click to dismiss (common UX pattern)
modules/user-directory/views/directory/template-parts/search-field.php (1)
17-21: Potential undefined variable access and default layout inconsistency.The template accesses
$_GET['search_by']safely with sanitization. However:
The default layout
'layout-1'on line 20 differs from the helper function's default of'layout-3'inHelpers.php(line 47). This inconsistency could cause unexpected styling.Variables
$block_idand$page_id(used on lines 29-30, 46-47) are used without existence checks, which could trigger PHP notices if the template is included without these being set.🔎 Proposed fix
// Get current search_by parameter from URL -$current_search_by = ! empty( $_GET['search_by'] ) ? sanitize_text_field( $_GET['search_by'] ) : ''; +$current_search_by = isset( $_GET['search_by'] ) ? sanitize_text_field( wp_unslash( $_GET['search_by'] ) ) : ''; // Get layout-specific colors -$layout = isset( $layout ) ? $layout : 'layout-1'; +$layout = isset( $layout ) ? $layout : 'layout-3'; $colors = wpuf_ud_get_layout_colors( $layout ); + +// Ensure block_id and page_id are defined +$block_id = isset( $block_id ) ? $block_id : ''; +$page_id = isset( $page_id ) ? $page_id : '';modules/user-directory/views/directory/template-parts/row-3.php (3)
37-40: Avatar HTML output should be explicitly allowed or escaped.The
$avatarvariable contains raw HTML fromwpuf_ud_get_user_avatar_html(). While the helper function performs internal escaping, echoing raw HTML withoutwp_kses_post()or similar could be a concern if the helper is modified later.Consider using
wp_kses_post()for defense-in-depth:<div class="wpuf-flex-shrink-0"> - <?php echo $avatar; ?> + <?php echo wp_kses_post( $avatar ); ?> </div>
116-125: Missingwp_unslash()on$_GETvalues.When accessing
$_GETsuperglobals, WordPress recommends usingwp_unslash()beforesanitize_text_field()to properly handle slashes added by PHP's magic quotes emulation.🔎 Proposed fix
} elseif ( isset( $_GET['page'] ) ) { - $current_page = intval( $_GET['page'] ); + $current_page = absint( wp_unslash( $_GET['page'] ) ); }
97-159: Profile URL generation logic is duplicated.The profile URL generation with state preservation (lines 100-153) appears to be duplicated across multiple row templates (row-1.php, row-3.php, etc. as mentioned in comments). Consider extracting this into a dedicated helper function in
Helpers.phpto reduce duplication and improve maintainability.assets/js/wpuf-user-directory-frontend.js (2)
22-31: Consider using event delegation for dynamically loaded elements.The search form handler uses direct binding (
.on('submit', ...)), which won't work for forms loaded via AJAX. If the search form can be dynamically replaced, consider delegating to a static parent.- $('.wpuf-ud-search-form').on('submit', function(e) { + $(document).on('submit', '.wpuf-ud-search-form', function(e) {
33-37: Pagination click handler may not persist after AJAX reload.Similar to the search form, pagination links may be replaced after AJAX requests. Using event delegation ensures the handler works after content updates.
- $('.wpuf-ud-pagination a').on('click', function() { - var $grid = $(this).closest('.wpuf-user-directory').find('.wpuf-ud-grid'); + $(document).on('click', '.wpuf-ud-pagination a', function() { + var $grid = $(this).closest('.wpuf-user-directory').find('.wpuf-ud-grid');modules/user-directory/views/profile/template-parts/posts-2.php (2)
4-6: Addwp_unslash()to$_GETaccess.WordPress recommends using
wp_unslash()when accessing superglobals.🔎 Proposed fix
// Get current page from query string for posts pagination -$posts_page = isset( $_GET['posts_page'] ) ? max( 1, intval( $_GET['posts_page'] ) ) : 1; +$posts_page = isset( $_GET['posts_page'] ) ? max( 1, absint( wp_unslash( $_GET['posts_page'] ) ) ) : 1; $posts_per_page = 10;
116-125: Avoid defining functions inside templates.Defining
wpuf_ud_build_posts_page_urlinside a template withfunction_existscheck is fragile. If multiple templates need this, define it once inHelpers.php. If it's template-specific, use a closure or inline the logic.Consider moving this function to
modules/user-directory/Helpers.phpalongside the existingwpuf_ud_build_page_urlfunction for consistency and reusability.src/js/user-directory/components/steps/StepLayout.js (3)
35-43: Layout display names should use i18n.The
layoutDisplayNamesobject contains hardcoded English strings. These should be translated using the__()function for consistency with the rest of the component.🔎 Proposed fix
// Custom display names for layouts const layoutDisplayNames = { - 'layout-1': 'Classic Table', - 'layout-2': 'Rounded Sidecard (2x)', - 'layout-3': 'Round Grid (3x)', - 'layout-4': 'Square Grid (3x)', - 'layout-5': 'Round Grid (4x)', - 'layout-6': 'Square Grid (4x)' + 'layout-1': __('Classic Table', 'wp-user-frontend'), + 'layout-2': __('Rounded Sidecard (2x)', 'wp-user-frontend'), + 'layout-3': __('Round Grid (3x)', 'wp-user-frontend'), + 'layout-4': __('Square Grid (3x)', 'wp-user-frontend'), + 'layout-5': __('Round Grid (4x)', 'wp-user-frontend'), + 'layout-6': __('Square Grid (4x)', 'wp-user-frontend') };
51-60: Remove empty style properties and fix letterSpacing value.There's an empty line in the style object (line 53), and
letterSpacing: '0%'is non-standard. CSSletter-spacingtypically usespx,em, ornormal.🔎 Proposed fix
<h2 className="wpuf-text-center" style={{ - fontWeight: 400, fontSize: '20px', lineHeight: '36px', - letterSpacing: '0%', + letterSpacing: 'normal', textAlign: 'center', color: '#000000' }}
114-116: Add fallback for missing asset URL.If both
wpuf_ud_freeandwpuf_udare undefined, the imagesrcwill be/images/pro-badge.svg, which may fail to load. Consider adding a more robust fallback.- src={(window.wpuf_ud_free?.asset_url || window.wpuf_ud?.asset_url || '') + '/images/pro-badge.svg'} + src={(window.wpuf_ud_free?.asset_url || window.wpuf_ud?.asset_url || '/wp-content/plugins/wp-user-frontend/modules/user-directory/assets') + '/images/pro-badge.svg'}Alternatively, consider showing a text-only "Pro" badge if the image fails to load.
modules/user-directory/views/directory/template-parts/pagination-shortcode.php (2)
28-29: Default layout inconsistency.The default layout
'layout-1'differs from the helper function's default of'layout-3'inHelpers.php. Consider aligning these for consistency.// Get layout-specific colors -$layout = isset( $layout ) ? $layout : 'layout-1'; +$layout = isset( $layout ) ? $layout : 'layout-3';
99-106: Escape URL outputs fromwpuf_ud_build_page_url.While
wpuf_ud_build_page_urlusesesc_url()internally (per the relevant code snippet), it's a best practice to escape at the point of output for defense-in-depth.The URLs on lines 100, 129, and 139 should use
esc_url():- <a href="<?php echo wpuf_ud_build_page_url( $base_url, $query_args, $current - 1 ); ?>" + <a href="<?php echo esc_url( wpuf_ud_build_page_url( $base_url, $query_args, $current - 1 ) ); ?>"Note: If
wpuf_ud_build_page_urlalready returns an escaped URL, this is redundant but harmless.Also applies to: 128-132, 138-145
modules/user-directory/views/profile/template-parts/comments-2.php (1)
5-5: Sanitize and validate$_GETinput.The
comments_pageparameter is read directly from$_GET. Whileintval()provides type safety, consider usingabsint()and add explicit sanitization for defense in depth.🔎 Proposed fix
-$comments_page = isset( $_GET['comments_page'] ) ? max( 1, intval( $_GET['comments_page'] ) ) : 1; +$comments_page = isset( $_GET['comments_page'] ) ? max( 1, absint( wp_unslash( $_GET['comments_page'] ) ) ) : 1;modules/user-directory/views/profile/template-parts/user-avatar.php (2)
50-51:$can_editis computed but unused.The variable is set but never referenced in the template output. Either remove it or implement the intended edit functionality.
31-45: Consider extracting initials logic to a shared helper.The comment notes this is "same logic as directory." Consider using a centralized helper like
wpuf_ud_get_user_initials()inHelpers.phpto avoid duplication and ensure consistency.assets/css/wpuf-user-directory-frontend.css (1)
13-15: Consider consolidating duplicate font-family declarations.The same system font stack is declared for both
.wpuf-user-directoryand.wpuf-profile. Consider using a shared class or CSS custom property to maintain consistency and reduce repetition.🔎 Example using CSS custom property
+:root { + --wpuf-font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; +} + .wpuf-user-directory { - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; + font-family: var(--wpuf-font-family); } .wpuf-profile { max-width: 800px; margin: 0 auto; padding: 1rem; - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; + font-family: var(--wpuf-font-family); }Also applies to: 270-275
src/js/user-directory/components/steps/StepBasics.js (1)
112-169: Consider extracting repeated inline styles to CSS classes or constants.The same style object pattern (maxWidth, height, borderRadius, padding, etc.) is duplicated across multiple input fields. Extracting to a shared constant or Tailwind utility classes would improve maintainability.
src/js/user-directory/components/DirectoryWizard.js (2)
119-126: Preferwp.apiFetchover nativefetchfor WordPress REST calls.Using
wp.apiFetchprovides automatic nonce handling, middleware support, and consistent error formatting. The current implementation manually handles nonce and URL construction, whichwp.apiFetchhandles automatically.🔎 Example refactor
+import apiFetch from '@wordpress/api-fetch'; + const handleSubmit = async () => { setLoading(true); setError(''); try { - const restUrl = config.rest_url || '/wp-json/'; - const url = formData.ID - ? `${restUrl}wpuf/v1/user_directory/${formData.ID}` - : `${restUrl}wpuf/v1/user_directory`; - - const response = await fetch(url, { - method: formData.ID ? 'PUT' : 'POST', - headers: { - 'Content-Type': 'application/json', - 'X-WP-Nonce': config.rest_nonce || '', - }, - body: JSON.stringify(formData), + const response = await apiFetch({ + path: formData.ID + ? `/wpuf/v1/user_directory/${formData.ID}` + : '/wpuf/v1/user_directory', + method: formData.ID ? 'PUT' : 'POST', + data: formData, });
266-267: Fragile DOM coupling for sidebar detection.Reading
document.body.classList.contains('folded')tightly couples this component to WordPress admin's sidebar implementation. Consider using a CSS-only solution or passing sidebar state via props/context.assets/js/ud-search-shortcode.js (1)
52-63: Add handling for network errors and non-JSON responses.The current error handling assumes all responses are JSON. Network failures or 5xx errors with HTML bodies will cause
res.json()to throw.🔎 Proposed fix
-fetch(apiUrl + '?' + params.toString(), { - credentials: 'same-origin', -}) - .then(res => res.json()) - .then(data => { - if (data && data.success) { - onSuccess(data); - } else { - onError(data); - } - }) - .catch(onError); +fetch(apiUrl + '?' + params.toString(), { + credentials: 'same-origin', +}) + .then(res => { + if (!res.ok) { + throw new Error(`HTTP ${res.status}`); + } + return res.json(); + }) + .then(data => { + if (data && data.success) { + onSuccess(data); + } else { + onError(data); + } + }) + .catch(onError);src/js/user-directory/components/common/MultiSelect.js (2)
1-4: Inconsistent indentation on line 4.There are extra leading spaces before
const MultiSelecton line 4. This appears to be a formatting issue.🔎 Proposed fix
import { useState, useRef, useEffect } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; - const MultiSelect = ({ options, value, onChange, placeholder, sortable = false, searchable = true, selectedLabel = 'items' }) => { +const MultiSelect = ({ options, value, onChange, placeholder, sortable = false, searchable = true, selectedLabel = 'items' }) => {
26-43: Fragile icon cloning assumes specific structure.The icon cloning logic assumes
option.icon.props.childrenexists and is a single clonable element. If the icon has multiple children or a different structure, this will fail silently or throw an error.Consider adding defensive checks:
const getOptionIcon = (key, isSelected = false) => { const option = options[key]; if (!option?.icon) return null; // Return icon as-is if cloning isn't safe const iconChildren = option.icon.props?.children; if (!iconChildren || typeof iconChildren !== 'object') { return option.icon; } // Proceed with cloning... };modules/user-directory/PrettyUrls.php (2)
146-171: Consider caching or optimizing the page query for large sites.
get_directory_pages()queries all published pages (posts_per_page => -1) and iterates through each to check for shortcodes. On sites with many pages, this could become a performance bottleneck since it runs on everyinithook.Consider using transient caching for the directory pages list:
private function get_directory_pages() { $cache_key = 'wpuf_ud_directory_pages'; $cached = get_transient( $cache_key ); if ( false !== $cached ) { return $cached; } $pages = get_posts( [ 'post_type' => 'page', 'post_status' => 'publish', 'posts_per_page' => -1, 'fields' => 'ids', // Optimize initial query ] ); // ... rest of logic set_transient( $cache_key, $directory_pages, HOUR_IN_SECONDS ); return $directory_pages; }Then invalidate the cache in
maybe_flush_rules().
182-199:flush_rewrite_rules()on every relevant page save may cause performance issues.Calling
flush_rewrite_rules()is an expensive operation that writes to the database. While the guard for autosave and post type is good, this still fires on every save of a page containing the shortcode.Consider deferring the flush or using a transient flag to batch flushes:
public function maybe_flush_rules( $post_id ) { if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) { return; } $post = get_post( $post_id ); if ( ! $post || 'page' !== $post->post_type ) { return; } if ( has_shortcode( $post->post_content, 'wpuf_user_listing' ) || has_shortcode( $post->post_content, 'wpuf_user_listing_id' ) ) { // Set a flag to flush on next admin_init instead of immediately update_option( 'wpuf_ud_flush_rewrite_rules', true ); } }Then flush during
admin_initif the flag is set.src/js/user-directory/App.js (2)
99-127: Consider replacingalert()with toast notifications for better UX.The delete error handling uses browser
alert()which blocks the UI and provides a jarring experience. The DirectoryWizard component appears to use a success toast pattern that could be adopted here for consistency.🔎 Example toast pattern
const [toast, setToast] = useState({ show: false, message: '', type: 'error' }); // In handleDelete error cases: setToast({ show: true, message: 'Failed to delete directory', type: 'error' }); // In JSX, render a toast component {toast.show && <Toast message={toast.message} type={toast.type} onClose={() => setToast({ show: false })} />}
16-24: ProBadge component is duplicated across files.This
ProBadgecomponent definition appears similar to one inDirectoryList.jsper the provided snippets. Consider extracting it to a shared location likecomponents/common/ProBadge.jsfor consistency and maintainability.src/js/user-directory/components/steps/StepAdvanced.js (2)
116-122: ProBadge component is duplicated across multiple files.This is the third instance of an inline
ProBadgecomponent in this PR (also inApp.jsand referenced inDirectoryList.js). Consider extracting to a shared component.Create
src/js/user-directory/components/common/ProBadge.js:import React from 'react'; const ProBadge = ({ small = false, className = '' }) => ( <img src={(window.wpuf_ud_free?.asset_url || window.wpuf_ud?.asset_url || window.wpuf_admin_script?.asset_url || '') + '/images/pro-badge.svg'} alt="Pro" className={className} style={{ width: small ? '30px' : '39px', height: small ? '17px' : '22px', display: 'inline-block', verticalAlign: 'middle' }} /> ); export default ProBadge;
67-94: Large inline style objects could be extracted to CSS.The
inputStyleandnumberInputStyleobjects contain substantial styling that could be moved to CSS classes for better maintainability and consistency with the Tailwind approach used elsewhere in the codebase.src/js/user-directory/components/steps/StepTabs.js (3)
65-65: Unused state setter can be simplified.The
enabledTabsstate setter is discarded but the state itself never changes. Consider usinguseMemoinstead ofuseStatefor a derived value that doesn't need to be updated.Proposed fix
- const [enabledTabs] = useState(() => getEnabledTabsArray(formData.profile_tabs)); + const enabledTabs = React.useMemo(() => getEnabledTabsArray(formData.profile_tabs), [formData.profile_tabs]);
167-187: Add keyboard accessibility for drag-and-drop reordering.The draggable tabs lack keyboard support for reordering. Screen reader users and keyboard-only users cannot reorder tabs. Consider adding
aria-label,role="listitem", and keyboard handlers for arrow key navigation.
338-338: Fixed width may cause layout issues on smaller screens.The content type buttons container uses a fixed
width: '590px'which may cause horizontal overflow on smaller viewports.Proposed fix
- <div className="wpuf-mt-4 wpuf-p-4 wpuf-bg-white wpuf-border wpuf-border-gray-200 wpuf-rounded-lg" style={{ width: '590px' }}> + <div className="wpuf-mt-4 wpuf-p-4 wpuf-bg-white wpuf-border wpuf-border-gray-200 wpuf-rounded-lg wpuf-max-w-full" style={{ width: '590px', maxWidth: '100%' }}>modules/user-directory/views/profile/layout-2.php (2)
154-156:str_word_countmay not work correctly for non-Latin text.The word counting uses
str_word_count()which is designed for Latin alphabets and may produce incorrect counts for CJK or other scripts. Consider using a more robust approach for internationalization.Alternative approach
// More i18n-friendly word count $word_count = count( preg_split( '/\s+/u', trim( $user_bio ), -1, PREG_SPLIT_NO_EMPTY ) );
458-537: Global JavaScript functions may conflict with other plugins.The functions
wpuf_ud_goBack()andwpuf_toggleBio_layout2()are defined in the global scope. Consider namespacing these under awpufobject to prevent potential conflicts.Proposed approach
// Define under namespace window.wpuf_ud = window.wpuf_ud || {}; window.wpuf_ud.goBack = function() { /* ... */ }; window.wpuf_ud.toggleBio = function(bioId) { /* ... */ };Then update the button onclick handlers accordingly.
modules/user-directory/DirectoryStyles.php (1)
314-319: Add nonce verification comment for direct$_GETaccess.While nonce verification isn't practical for URL parameters used in page detection, adding a PHPCS ignore comment would clarify this is intentional and improve code clarity.
Proposed fix
foreach ( $profile_params as $param ) { + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- URL params for profile detection if ( ! empty( $_GET[$param] ) ) { // Additional validation: ensure we're on a directory page return $this->is_directory_page(); } }modules/user-directory/User_Directory.php (2)
109-113: Public constructor undermines singleton pattern.The constructor is
publicwhile the class uses a singleton pattern viaget_instance(). This allows bypassing the singleton by callingnew User_Directory()directly. Consider making the constructorprivate.Proposed fix
- public function __construct() { + private function __construct() { $this->define_constants(); $this->includes(); $this->init_hooks(); }
223-232: Consider usingwp_count_posts()for efficiency.Using
get_postswithposts_per_page => -1loads all directory posts into memory just to count them. For a simple count,wp_count_posts()is more efficient.Proposed fix
public static function get_directory_count() { - $directories = get_posts( [ - 'post_type' => self::POST_TYPE, - 'post_status' => 'any', - 'posts_per_page' => -1, - 'fields' => 'ids', - ] ); - - return count( $directories ); + $counts = wp_count_posts( self::POST_TYPE ); + $total = 0; + foreach ( $counts as $status => $count ) { + $total += (int) $count; + } + return $total; }modules/user-directory/Shortcode.php (1)
463-484:extract()usage is documented but consider alternatives.While the PHPCS ignore comment is present,
extract()makes it harder to trace variable origins in templates. Consider passing$datadirectly and accessing keys explicitly in templates for better maintainability.
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (22)
assets/css/wpuf-user-directory-free.css.mapis excluded by!**/*.mapassets/images/user-directory/confetti.pngis excluded by!**/*.pngassets/images/user-directory/directory-layout-1.pngis excluded by!**/*.pngassets/images/user-directory/directory-layout-2.pngis excluded by!**/*.pngassets/images/user-directory/directory-layout-3.pngis excluded by!**/*.pngassets/images/user-directory/directory-layout-4.pngis excluded by!**/*.pngassets/images/user-directory/directory-layout-5.pngis excluded by!**/*.pngassets/images/user-directory/directory-layout-6.pngis excluded by!**/*.pngassets/images/user-directory/profile-layout-1.pngis excluded by!**/*.pngassets/images/user-directory/profile-layout-2.pngis excluded by!**/*.pngassets/images/user-directory/profile-layout-3.pngis excluded by!**/*.pngassets/images/user-directory/round-grids.pngis excluded by!**/*.pngassets/images/user-directory/sidecards.pngis excluded by!**/*.pngassets/images/user-directory/square-grids.pngis excluded by!**/*.pngassets/images/user-directory/table.pngis excluded by!**/*.pngassets/images/user-directory/thumb-male-1.svgis excluded by!**/*.svgassets/images/user-directory/thumb-male-2.svgis excluded by!**/*.svgassets/images/user-directory/thumb-male-3.svgis excluded by!**/*.svgassets/images/user-directory/wide-sidecards.pngis excluded by!**/*.pngassets/js/wpuf-user-directory-free.js.mapis excluded by!**/*.mapmodules/user-directory/package-lock.jsonis excluded by!**/package-lock.jsonpackage-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (62)
Gruntfile.jsassets/css/admin/subscriptions.min.cssassets/css/admin/wpuf-module.cssassets/css/ai-form-builder.min.cssassets/css/forms-list.min.cssassets/css/frontend-subscriptions.min.cssassets/css/wpuf-user-directory-free.cssassets/css/wpuf-user-directory-frontend.cssassets/js/admin/wpuf-module.jsassets/js/ud-search-shortcode.jsassets/js/wpuf-user-directory-free.asset.phpassets/js/wpuf-user-directory-free.jsassets/js/wpuf-user-directory-free.js.LICENSE.txtassets/js/wpuf-user-directory-frontend.jsincludes/Free/Free_Loader.phpincludes/functions/modules.phpmodules/user-directory/Admin_Menu.phpmodules/user-directory/Api/Directory.phpmodules/user-directory/DirectoryStyles.phpmodules/user-directory/Helpers.phpmodules/user-directory/Post_Type.phpmodules/user-directory/PrettyUrls.phpmodules/user-directory/Shortcode.phpmodules/user-directory/User_Directory.phpmodules/user-directory/package.jsonmodules/user-directory/postcss.config.jsmodules/user-directory/tailwind.config.jsmodules/user-directory/views/admin-page.phpmodules/user-directory/views/directory/layout-3.phpmodules/user-directory/views/directory/template-parts/pagination-shortcode.phpmodules/user-directory/views/directory/template-parts/row-3.phpmodules/user-directory/views/directory/template-parts/search-field.phpmodules/user-directory/views/directory/template-parts/social-icons.phpmodules/user-directory/views/directory/template-parts/sort-field.phpmodules/user-directory/views/profile/layout-2.phpmodules/user-directory/views/profile/template-parts/about-2.phpmodules/user-directory/views/profile/template-parts/comments-2.phpmodules/user-directory/views/profile/template-parts/file-2.phpmodules/user-directory/views/profile/template-parts/posts-2.phpmodules/user-directory/views/profile/template-parts/user-avatar.phpmodules/user-directory/webpack.config.jspackage.jsonpostcss.user-directory.config.jssrc/js/user-directory/App.jssrc/js/user-directory/components/DirectoryList.jssrc/js/user-directory/components/DirectoryWizard.jssrc/js/user-directory/components/common/DeleteConfirmModal.jssrc/js/user-directory/components/common/Header.jssrc/js/user-directory/components/common/LayoutCard.jssrc/js/user-directory/components/common/MultiSelect.jssrc/js/user-directory/components/common/Toast.jssrc/js/user-directory/components/common/Tooltip.jssrc/js/user-directory/components/steps/StepAdvanced.jssrc/js/user-directory/components/steps/StepBasics.jssrc/js/user-directory/components/steps/StepLayout.jssrc/js/user-directory/components/steps/StepProfile.jssrc/js/user-directory/components/steps/StepTabs.jssrc/js/user-directory/index.jssrc/js/user-directory/styles/main.csssrc/js/user-directory/utils/avatarSizeHelper.jstailwind.config.jswpuf-functions.php
🧰 Additional context used
🧬 Code graph analysis (23)
modules/user-directory/views/directory/template-parts/pagination-shortcode.php (2)
assets/js/ud-search-shortcode.js (2)
pagination(76-76)layout(84-84)modules/user-directory/Helpers.php (3)
wpuf_ud_get_profile_layout_colors(61-63)wpuf_ud_get_layout_colors(48-50)wpuf_ud_build_page_url(76-119)
src/js/user-directory/utils/avatarSizeHelper.js (1)
assets/js/ud-search-shortcode.js (1)
layout(84-84)
assets/js/wpuf-user-directory-frontend.js (1)
assets/js/ud-search-shortcode.js (1)
params(36-36)
modules/user-directory/views/profile/template-parts/file-2.php (1)
Lib/WeDevs_Settings_API.php (1)
get_option(649-658)
src/js/user-directory/index.js (1)
src/js/user-directory/App.js (1)
App(26-216)
src/js/user-directory/App.js (4)
src/js/user-directory/components/DirectoryList.js (2)
ProBadge(13-20)DirectoryList(22-326)src/js/user-directory/components/DirectoryWizard.js (3)
loading(36-36)error(37-37)DirectoryWizard(34-327)src/js/user-directory/components/common/Header.js (2)
Header(13-85)wpuf(15-15)src/js/user-directory/components/common/DeleteConfirmModal.js (1)
DeleteConfirmModal(12-63)
modules/user-directory/views/directory/template-parts/row-3.php (1)
modules/user-directory/Helpers.php (2)
wpuf_ud_get_user_avatar_html(186-239)wpuf_ud_get_profile_url(251-366)
src/js/user-directory/components/DirectoryList.js (3)
src/js/user-directory/App.js (1)
ProBadge(17-24)src/js/user-directory/components/steps/StepAdvanced.js (1)
ProBadge(116-122)src/js/user-directory/components/steps/StepTabs.js (1)
defaultTabs(6-10)
src/js/user-directory/components/steps/StepProfile.js (3)
src/js/user-directory/components/steps/StepLayout.js (4)
freeLayout(7-7)handleChange(9-28)layoutOptions(31-33)isProLayout(46-46)src/js/user-directory/components/steps/StepBasics.js (1)
handleChange(34-37)src/js/user-directory/components/DirectoryWizard.js (1)
formData(35-35)
modules/user-directory/PrettyUrls.php (4)
modules/user-directory/User_Directory.php (2)
User_Directory(24-320)__construct(109-113)modules/user-directory/Admin_Menu.php (1)
__construct(31-34)modules/user-directory/Post_Type.php (1)
__construct(31-33)modules/user-directory/Shortcode.php (1)
__construct(31-33)
src/js/user-directory/components/steps/StepAdvanced.js (3)
src/js/user-directory/components/steps/StepLayout.js (1)
handleChange(9-28)src/js/user-directory/components/steps/StepProfile.js (1)
handleChange(8-17)src/js/user-directory/components/common/Tooltip.js (1)
Tooltip(3-34)
modules/user-directory/views/profile/template-parts/user-avatar.php (1)
modules/user-directory/Helpers.php (1)
wpuf_ud_get_avatar_url(131-173)
modules/user-directory/views/directory/template-parts/search-field.php (1)
modules/user-directory/Helpers.php (1)
wpuf_ud_get_layout_colors(48-50)
modules/user-directory/User_Directory.php (2)
includes/Free/Free_Loader.php (2)
admin_menu(190-203)includes(78-87)wpuf-functions.php (1)
wpuf_is_pro_active(5045-5047)
modules/user-directory/views/profile/layout-2.php (3)
modules/user-directory/Helpers.php (3)
wpuf_ud_get_profile_data(379-523)wpuf_ud_get_cover_photo_data(735-764)wpuf_ud_get_tab_label(617-632)src/js/user-directory/App.js (1)
config(36-36)wpuf-functions.php (1)
wpuf_get_option(1546-1554)
modules/user-directory/Api/Directory.php (2)
modules/user-directory/User_Directory.php (3)
User_Directory(24-320)has_reached_limit(206-214)get_default_settings(286-319)wpuf-functions.php (1)
wpuf_is_pro_active(5045-5047)
modules/user-directory/DirectoryStyles.php (2)
modules/user-directory/PrettyUrls.php (1)
__construct(31-55)modules/user-directory/Shortcode.php (1)
__construct(31-33)
src/js/user-directory/components/steps/StepTabs.js (2)
src/js/user-directory/App.js (1)
config(36-36)assets/js/wpuf-user-directory-free.js (1)
e(2-2)
modules/user-directory/views/profile/template-parts/posts-2.php (2)
assets/js/ud-search-shortcode.js (1)
pagination(76-76)tests/e2e/utils/generate-summary.js (1)
total(217-217)
modules/user-directory/views/directory/layout-3.php (1)
assets/js/ud-search-shortcode.js (1)
layout(84-84)
includes/functions/modules.php (1)
Lib/WeDevs_Settings_API.php (1)
get_option(649-658)
src/js/user-directory/components/steps/StepLayout.js (5)
src/js/user-directory/components/steps/StepProfile.js (4)
freeLayout(6-6)handleChange(8-17)layoutOptions(20-22)isProLayout(25-25)src/js/user-directory/components/steps/StepAdvanced.js (1)
handleChange(14-65)src/js/user-directory/components/steps/StepBasics.js (1)
handleChange(34-37)src/js/user-directory/utils/avatarSizeHelper.js (2)
getOptimalAvatarSize(7-17)getOptimalAvatarSize(7-17)src/js/user-directory/components/DirectoryWizard.js (1)
formData(35-35)
includes/Free/Free_Loader.php (2)
wpuf-functions.php (1)
wpuf_is_pro_active(5045-5047)includes/functions/modules.php (4)
wpuf_free_is_module_active(67-69)wpuf_free_get_modules(23-34)wpuf_free_activate_module(93-110)wpuf_free_deactivate_module(121-136)
🪛 PHPMD (2.15.0)
modules/user-directory/Api/Directory.php
131-131: Avoid unused parameters such as '$request'. (undefined)
(UnusedFormalParameter)
513-513: Avoid unused local variables such as '$user'. (undefined)
(UnusedLocalVariable)
515-515: Avoid unused local variables such as '$all_data'. (undefined)
(UnusedLocalVariable)
534-534: Avoid unused local variables such as '$pagination'. (undefined)
(UnusedLocalVariable)
550-550: Avoid unused local variables such as '$layout'. (undefined)
(UnusedLocalVariable)
modules/user-directory/DirectoryStyles.php
80-80: Avoid unused parameters such as '$parsed_block'. (undefined)
(UnusedFormalParameter)
80-80: Avoid unused parameters such as '$block'. (undefined)
(UnusedFormalParameter)
412-412: Avoid unused parameters such as '$post'. (undefined)
(UnusedFormalParameter)
modules/user-directory/Shortcode.php
493-505: Avoid unused private methods such as 'get_current_page'. (undefined)
(UnusedPrivateMethod)
modules/user-directory/Helpers.php
25-25: Avoid unused parameters such as '$layout'. (undefined)
(UnusedFormalParameter)
645-645: Avoid unused parameters such as '$type'. (undefined)
(UnusedFormalParameter)
721-721: Avoid unused parameters such as '$user'. (undefined)
(UnusedFormalParameter)
776-776: Avoid unused parameters such as '$user_id'. (undefined)
(UnusedFormalParameter)
776-776: Avoid unused parameters such as '$meta_key'. (undefined)
(UnusedFormalParameter)
793-793: Avoid unused parameters such as '$field'. (undefined)
(UnusedFormalParameter)
793-793: Avoid unused parameters such as '$user'. (undefined)
(UnusedFormalParameter)
793-793: Avoid unused parameters such as '$user_id'. (undefined)
(UnusedFormalParameter)
793-793: Avoid unused parameters such as '$layout'. (undefined)
(UnusedFormalParameter)
817-817: Avoid unused parameters such as '$class'. (undefined)
(UnusedFormalParameter)
832-832: Avoid unused parameters such as '$field'. (undefined)
(UnusedFormalParameter)
832-832: Avoid unused parameters such as '$user_id'. (undefined)
(UnusedFormalParameter)
832-832: Avoid unused parameters such as '$layout'. (undefined)
(UnusedFormalParameter)
848-848: Avoid unused parameters such as '$field'. (undefined)
(UnusedFormalParameter)
848-848: Avoid unused parameters such as '$user_id'. (undefined)
(UnusedFormalParameter)
848-848: Avoid unused parameters such as '$template_data'. (undefined)
(UnusedFormalParameter)
848-848: Avoid unused parameters such as '$color'. (undefined)
(UnusedFormalParameter)
| 'use strict'; | ||
|
|
||
| const SEARCH_DEBOUNCE = 300; | ||
| let debounceTimeout = null; |
There was a problem hiding this comment.
Shared debounceTimeout variable causes cross-listing interference.
The debounceTimeout variable is declared at the module scope but used across all listing instances. If multiple directories exist on a page, typing in one will cancel the debounce timer of another.
🔎 Proposed fix — move timeout inside initUserDirectorySearch
-(function(window, document) {
- 'use strict';
-
- const SEARCH_DEBOUNCE = 300;
- let debounceTimeout = null;
+(function(window, document) {
+ 'use strict';
+
+ const SEARCH_DEBOUNCE = 300;
function initUserDirectorySearch(container, blockId, pageId) {
let currentRequestId = 0;
+ let debounceTimeout = null;
// ... rest of function
input.addEventListener('input', function(e) {
const value = e.target.value.trim();
if (debounceTimeout) clearTimeout(debounceTimeout);
debounceTimeout = setTimeout(() => {
performSearch(value, 1);
}, SEARCH_DEBOUNCE);
});Also applies to: 335-338
🤖 Prompt for AI Agents
In assets/js/ud-search-shortcode.js around line 14 (and similarly lines
335-338), the debounceTimeout variable is declared at module scope causing
debounce timers to collide across multiple directory instances; move the
debounceTimeout declaration into the initUserDirectorySearch function (so each
instance gets its own timeout variable), update any references inside that
function to use the local variable, and remove or rename the module-level
variable to avoid shared state between listings.
| // Public search endpoint (no auth required for frontend AJAX) | ||
| register_rest_route( | ||
| $this->namespace, | ||
| '/' . $this->rest_base . '/search', | ||
| [ | ||
| [ | ||
| 'methods' => WP_REST_Server::READABLE, | ||
| 'callback' => [ $this, 'search_users' ], | ||
| 'permission_callback' => '__return_true', // Public endpoint | ||
| ], | ||
| ] | ||
| ); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# Search for rate limiting or throttling mechanisms
rg -n "rate.?limit|throttl|rateLimit|throttle|limit.*request" --type=php -g '!vendor/*' -g '!node_modules/*' -iRepository: weDevsOfficial/wp-user-frontend
Length of output: 1112
🏁 Script executed:
# Also check for any middleware or protection mechanisms
rg -n "middleware|guard|protect|security" --type=php modules/user-directory/ -iRepository: weDevsOfficial/wp-user-frontend
Length of output: 1051
🏁 Script executed:
# Check if there's any documentation about this endpoint
find . -name "README*" -o -name "*.md" | xargs rg -l "rate.?limit|search.*endpoint|directory.*search" -i 2>/dev/null | head -20Repository: weDevsOfficial/wp-user-frontend
Length of output: 57
🏁 Script executed:
# Look for any WordPress hooks or filters that might handle rate limiting
rg -n "wp_rest_|rest_.*filter|do_action.*rest" --type=php modules/user-directory/ -iRepository: weDevsOfficial/wp-user-frontend
Length of output: 2726
🏁 Script executed:
# Get the search_users method implementation
sed -n '241,280p' modules/user-directory/Api/Directory.phpRepository: weDevsOfficial/wp-user-frontend
Length of output: 1414
🏁 Script executed:
# Check if there are any filters or actions applied in the callback
rg -n "search_users|apply_filters.*search|do_action.*search" modules/user-directory/ -A 5Repository: weDevsOfficial/wp-user-frontend
Length of output: 1382
🏁 Script executed:
# Check the full Directory.php file to understand the scope better
wc -l modules/user-directory/Api/Directory.phpRepository: weDevsOfficial/wp-user-frontend
Length of output: 119
🏁 Script executed:
# Get the full search_users method implementation
sed -n '440,520p' modules/user-directory/Api/Directory.phpRepository: weDevsOfficial/wp-user-frontend
Length of output: 3941
Public search endpoint lacks rate limiting and should be protected.
The /search endpoint is public (__return_true permission) which is necessary for frontend AJAX, but has no rate limiting. The search_users() method accepts pagination parameters (page, max_item) without any throttling mechanism, making it vulnerable to request flooding or user data scraping attacks. Implement rate limiting at the application level (e.g., via filters or a middleware layer) or document that reliance on server/CDN-level protection is required.
| // Flush rewrite rules for pretty URLs | ||
| flush_rewrite_rules(); |
There was a problem hiding this comment.
Avoid flush_rewrite_rules() on every directory creation.
Calling flush_rewrite_rules() on every directory creation is expensive and can cause performance issues. Consider flushing only when the first directory is created, or defer to a scheduled event.
Proposed fix
- // Flush rewrite rules for pretty URLs
- flush_rewrite_rules();
+ // Schedule rewrite rules flush for next page load (avoids performance hit)
+ if ( ! get_option( 'wpuf_ud_flush_rewrite_rules' ) ) {
+ update_option( 'wpuf_ud_flush_rewrite_rules', true );
+ }Then handle the flush on init hook:
add_action( 'init', function() {
if ( get_option( 'wpuf_ud_flush_rewrite_rules' ) ) {
flush_rewrite_rules();
delete_option( 'wpuf_ud_flush_rewrite_rules' );
}
}, 99 );Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In modules/user-directory/Api/Directory.php around lines 288-289, calling
flush_rewrite_rules() on every directory creation is expensive; remove the
direct flush_rewrite_rules() call and instead set a transient/option (e.g.
wpuf_ud_flush_rewrite_rules) when a directory is created to mark that rewrites
need flushing, then add an init hook (priority ~99) that checks that option,
calls flush_rewrite_rules() once, and deletes the option after flushing so the
expensive operation runs only once (or via a scheduled event) rather than on
every creation.
There was a problem hiding this comment.
@arifulhoque7 bhai. flush_rewrite_rules() is an expensive function call. Minimize it as much as possible
There was a problem hiding this comment.
Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!
| $is_shortcode = empty( $block_id ) || strpos( $block_id, 'shortcode_' ) === 0; | ||
|
|
||
| if ( $is_shortcode ) { | ||
| // Use shortcode-specific pagination template | ||
| $layout = ! empty( $directory_layout ) ? $directory_layout : 'layout-3'; | ||
|
|
||
| // Prepare query args for pagination links | ||
| $current_url = ( is_ssl() ? 'https://' : 'http://' ) . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']; | ||
| $parsed_url = wp_parse_url( $current_url ); | ||
| $base_url = $parsed_url['path'] ?? ''; | ||
|
|
||
| $query_args = []; | ||
| if ( ! empty( $parsed_url['query'] ) ) { | ||
| parse_str( $parsed_url['query'], $query_args ); | ||
| } | ||
|
|
||
| // Ensure directory_layout is included in pagination links | ||
| $query_args['directory_layout'] = $layout; | ||
| unset( $query_args['page'] ); // Will be set per link | ||
|
|
||
| include WPUF_UD_FREE_TEMPLATES . '/directory/template-parts/pagination-shortcode.php'; | ||
| } else { | ||
| // Use default block pagination template | ||
| $layout = ! empty( $directory_layout ) ? $directory_layout : 'layout-3'; | ||
| include WPUF_UD_FREE_TEMPLATES . '/directory/template-parts/pagination-shortcode.php'; | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
Redundant conditional — both branches execute the same code.
The $is_shortcode check differentiates between shortcode and block contexts, but both branches set $layout identically and include the same pagination-shortcode.php template. Either consolidate the logic or implement the intended block-specific pagination.
🔎 Proposed simplification
<?php
- // Use different pagination template based on context
- // Check if this is a shortcode context (not a block context)
- $is_shortcode = empty( $block_id ) || strpos( $block_id, 'shortcode_' ) === 0;
-
- if ( $is_shortcode ) {
- // Use shortcode-specific pagination template
- $layout = ! empty( $directory_layout ) ? $directory_layout : 'layout-3';
-
- // Prepare query args for pagination links
- $current_url = ( is_ssl() ? 'https://' : 'http://' ) . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];
- $parsed_url = wp_parse_url( $current_url );
- $base_url = $parsed_url['path'] ?? '';
-
- $query_args = [];
- if ( ! empty( $parsed_url['query'] ) ) {
- parse_str( $parsed_url['query'], $query_args );
- }
-
- // Ensure directory_layout is included in pagination links
- $query_args['directory_layout'] = $layout;
- unset( $query_args['page'] ); // Will be set per link
-
- include WPUF_UD_FREE_TEMPLATES . '/directory/template-parts/pagination-shortcode.php';
- } else {
- // Use default block pagination template
- $layout = ! empty( $directory_layout ) ? $directory_layout : 'layout-3';
- include WPUF_UD_FREE_TEMPLATES . '/directory/template-parts/pagination-shortcode.php';
- }
+ $layout = ! empty( $directory_layout ) ? $directory_layout : 'layout-3';
+
+ // Prepare query args for pagination links
+ $current_url = home_url( add_query_arg( null, null ) );
+ $parsed_url = wp_parse_url( $current_url );
+ $base_url = $parsed_url['path'] ?? '';
+
+ $query_args = [];
+ if ( ! empty( $parsed_url['query'] ) ) {
+ parse_str( $parsed_url['query'], $query_args );
+ }
+
+ // Ensure directory_layout is included in pagination links
+ $query_args['directory_layout'] = $layout;
+ unset( $query_args['page'] ); // Will be set per link
+
+ include WPUF_UD_FREE_TEMPLATES . '/directory/template-parts/pagination-shortcode.php';
?>🤖 Prompt for AI Agents
In modules/user-directory/views/directory/layout-3.php around lines 65 to 90,
the $is_shortcode conditional is redundant because both branches set $layout the
same way and include the same pagination-shortcode.php; remove the if/else and
consolidate into a single block that sets $layout = ! empty($directory_layout) ?
$directory_layout : 'layout-3', prepares the
$current_url/$parsed_url/$query_args logic only if needed (or always, if
pagination relies on it), ensures $query_args['directory_layout'] is set and
'page' unset, then include the pagination-shortcode.php once; alternatively, if
block-specific behavior was intended, replace the else branch with the correct
block-specific pagination template and logic instead of duplicating the
shortcode branch.
| if ( ! function_exists( 'wpuf_get_image_size_dimensions_file2' ) ) { | ||
| function wpuf_get_image_size_dimensions_file2( $size ) { | ||
| global $_wp_additional_image_sizes; | ||
|
|
||
| // If it's already a number, use it directly | ||
| if ( is_numeric( $size ) ) { | ||
| return array( 'size' => intval( $size ), 'wp_size' => 'custom' ); | ||
| } | ||
|
|
||
| // Default WordPress sizes | ||
| $default_sizes = array( | ||
| 'thumbnail' => array( 'width' => get_option( 'thumbnail_size_w', 150 ), 'height' => get_option( 'thumbnail_size_h', 150 ) ), | ||
| 'medium' => array( 'width' => get_option( 'medium_size_w', 300 ), 'height' => get_option( 'medium_size_h', 300 ) ), | ||
| 'medium_large' => array( 'width' => get_option( 'medium_large_size_w', 768 ), 'height' => get_option( 'medium_large_size_h', 0 ) ), | ||
| 'large' => array( 'width' => get_option( 'large_size_w', 1024 ), 'height' => get_option( 'large_size_h', 1024 ) ), | ||
| 'full' => array( 'width' => 1536, 'height' => 1536 ) // Fallback for full size | ||
| ); | ||
|
|
||
| // Check default sizes first | ||
| if ( isset( $default_sizes[ $size ] ) ) { | ||
| $width = $default_sizes[ $size ]['width']; | ||
| return array( 'size' => $width > 0 ? $width : 150, 'wp_size' => $size ); | ||
| } | ||
|
|
||
| // Check additional custom sizes | ||
| if ( isset( $_wp_additional_image_sizes[ $size ] ) ) { | ||
| $width = $_wp_additional_image_sizes[ $size ]['width']; | ||
| return array( 'size' => $width > 0 ? $width : 150, 'wp_size' => $size ); | ||
| } | ||
|
|
||
| // Fallback to thumbnail if size not found | ||
| return array( 'size' => get_option( 'thumbnail_size_w', 150 ), 'wp_size' => 'thumbnail' ); | ||
| } | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
Avoid defining global functions inside templates.
Defining wpuf_get_image_size_dimensions_file2 as a global function inside a template is an anti-pattern. The function_exists check suggests awareness of potential conflicts, but the suffixed name is a code smell.
Move this to modules/user-directory/Helpers.php as a static method:
// In Helpers.php
public static function get_image_size_dimensions( $size ) {
// ... implementation
}Then call it as:
$size_info = \WeDevs\Wpuf\Modules\User_Directory\Helpers::get_image_size_dimensions( $size_value );|
Directory members are not showing in list (frontend) @arifulhoque7 vai 521296897-c0d5129f-fcc8-46ac-a9b7-580b7c412f55.mov |
Updated both Directory API and Shortcode to skip applying the 'role__in' filter if 'all' is present in the roles array. This prevents unnecessary filtering when all roles should be included.
@Rubaiyat-E-Mohammad vai, now check please. It should be fixed
|
There was a problem hiding this comment.
Actionable comments posted: 3
♻️ Duplicate comments (2)
modules/user-directory/Api/Directory.php (2)
95-106: Previous concern about rate limiting on public search endpoint remains valid.As noted in the previous review, this public endpoint (
__return_truepermission) lacks rate limiting protection. While public access is necessary for frontend AJAX functionality, consider implementing application-level throttling or documenting that server/CDN-level protection is required.
288-289: Previous concern about expensiveflush_rewrite_rules()call remains valid.As noted in the previous review, calling
flush_rewrite_rules()on every directory creation is expensive. Consider implementing the suggested deferred flush pattern using an option flag checked oninit.
🧹 Nitpick comments (2)
modules/user-directory/Shortcode.php (2)
147-155: Consider adding validation for non-numeric directory IDs.The
render_shortcode_with_idmethod accepts any value from the shortcode attributes array and passes it torender_from_stored_settings. Whileabsint()handles this at line 167, consider adding early validation here to provide clearer behavior for invalid inputs.🔎 Suggested improvement
public function render_shortcode_with_id( $atts ) { if ( is_array( $atts ) && ! empty( $atts ) ) { $directory_id = reset( $atts ); + + if ( ! is_numeric( $directory_id ) ) { + return ''; + } return $this->render_from_stored_settings( $directory_id ); } return ''; }
446-448: Circular reference for template compatibility is acceptable but could be clearer.The self-referential assignment
$profile_data['template_data'] = $profile_datacreates a circular structure for Pro template compatibility. This works but a brief inline comment explaining the reason would improve maintainability.
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
modules/user-directory/Api/Directory.phpmodules/user-directory/Shortcode.php
🧰 Additional context used
🧬 Code graph analysis (2)
modules/user-directory/Api/Directory.php (2)
modules/user-directory/User_Directory.php (3)
User_Directory(24-320)has_reached_limit(206-214)get_default_settings(286-319)wpuf-functions.php (1)
wpuf_is_pro_active(5045-5047)
modules/user-directory/Shortcode.php (1)
modules/user-directory/User_Directory.php (3)
User_Directory(24-320)get_first_directory(241-251)get_default_settings(286-319)
🪛 PHPMD (2.15.0)
modules/user-directory/Api/Directory.php
131-131: Avoid unused parameters such as '$request'. (undefined)
(UnusedFormalParameter)
516-516: Avoid unused local variables such as '$user'. (undefined)
(UnusedLocalVariable)
518-518: Avoid unused local variables such as '$all_data'. (undefined)
(UnusedLocalVariable)
537-537: Avoid unused local variables such as '$pagination'. (undefined)
(UnusedLocalVariable)
553-553: Avoid unused local variables such as '$layout'. (undefined)
(UnusedLocalVariable)
modules/user-directory/Shortcode.php
496-508: Avoid unused private methods such as 'get_current_page'. (undefined)
(UnusedPrivateMethod)
🔇 Additional comments (6)
modules/user-directory/Shortcode.php (2)
31-67: LGTM!Good use of the filter pattern to allow Pro version to override shortcode registration. The hook structure is clean and extensible.
261-314: LGTM!The directory rendering logic is well-structured with proper sanitization, sensible defaults, and good Pro compatibility. The role filtering correctly handles the 'all' case, and the search implementation follows WordPress patterns.
modules/user-directory/Api/Directory.php (4)
152-202: LGTM!The
get_itemsimplementation properly handles pagination, search, and post data reset. Response structure is consistent and well-formed.
396-429: LGTM!The
get_user_countmethod correctly handles role filtering, user exclusions, and max item caps. The use ofcount_totalis appropriate for counting without fetching user data.
510-532: Static analysis false positives - variables are used by included templates.The variables
$user,$all_data,$pagination, and$layoutflagged as unused by PHPMD are actually consumed by the included template files (row-3.phpandpagination-shortcode.php). This is a common pattern in WordPress template rendering.
669-716: LGTM!The
enforce_free_limitsmethod properly constrains settings for free users while providing filter hooks for Pro to bypass restrictions. The validation of allowed values for avatar and profile sizes is well-implemented.
| public function get_profile_url( $user, $settings = [] ) { | ||
| $profile_base = $settings['profile_base'] ?? 'username'; | ||
| $current_url = get_permalink(); | ||
|
|
||
| if ( 'user_id' === $profile_base ) { | ||
| $slug = $user->ID; | ||
| } else { | ||
| $slug = $user->user_login; | ||
| } | ||
|
|
||
| // Try pretty URL first | ||
| $pretty_url = trailingslashit( $current_url ) . 'user/' . $slug; | ||
|
|
||
| // Check if pretty URLs are enabled | ||
| if ( get_option( 'permalink_structure' ) ) { | ||
| return esc_url( $pretty_url ); | ||
| } | ||
|
|
||
| // Fallback to query parameter | ||
| return esc_url( add_query_arg( 'wpuf_user', $user->ID, $current_url ) ); | ||
| } |
There was a problem hiding this comment.
Potential URL encoding issue with special characters in usernames.
When profile_base is 'username', the user_login is used directly in the URL without encoding. Usernames containing special characters (spaces, non-ASCII) could produce malformed URLs. Consider using rawurlencode().
🔎 Proposed fix
public function get_profile_url( $user, $settings = [] ) {
$profile_base = $settings['profile_base'] ?? 'username';
$current_url = get_permalink();
if ( 'user_id' === $profile_base ) {
$slug = $user->ID;
} else {
- $slug = $user->user_login;
+ $slug = rawurlencode( $user->user_login );
}
// Try pretty URL first
$pretty_url = trailingslashit( $current_url ) . 'user/' . $slug;📝 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.
| public function get_profile_url( $user, $settings = [] ) { | |
| $profile_base = $settings['profile_base'] ?? 'username'; | |
| $current_url = get_permalink(); | |
| if ( 'user_id' === $profile_base ) { | |
| $slug = $user->ID; | |
| } else { | |
| $slug = $user->user_login; | |
| } | |
| // Try pretty URL first | |
| $pretty_url = trailingslashit( $current_url ) . 'user/' . $slug; | |
| // Check if pretty URLs are enabled | |
| if ( get_option( 'permalink_structure' ) ) { | |
| return esc_url( $pretty_url ); | |
| } | |
| // Fallback to query parameter | |
| return esc_url( add_query_arg( 'wpuf_user', $user->ID, $current_url ) ); | |
| } | |
| public function get_profile_url( $user, $settings = [] ) { | |
| $profile_base = $settings['profile_base'] ?? 'username'; | |
| $current_url = get_permalink(); | |
| if ( 'user_id' === $profile_base ) { | |
| $slug = $user->ID; | |
| } else { | |
| $slug = rawurlencode( $user->user_login ); | |
| } | |
| // Try pretty URL first | |
| $pretty_url = trailingslashit( $current_url ) . 'user/' . $slug; | |
| // Check if pretty URLs are enabled | |
| if ( get_option( 'permalink_structure' ) ) { | |
| return esc_url( $pretty_url ); | |
| } | |
| // Fallback to query parameter | |
| return esc_url( add_query_arg( 'wpuf_user', $user->ID, $current_url ) ); | |
| } |
🤖 Prompt for AI Agents
In modules/user-directory/Shortcode.php around lines 533 to 553, the code uses
$user->user_login directly in the URL which can produce malformed URLs for
usernames with spaces or non-ASCII characters; change construction so the slug
is rawurlencode($user->user_login) when profile_base !== 'user_id' (leave
numeric ID branch untouched), then build the pretty_url as before and return
esc_url(...) — this ensures special characters are percent-encoded before
escaping and output.
Screen.Recording.2025-12-26.at.2.30.59.PM.mov
|
Updated default and saved settings to include all profile tabs for better Pro compatibility. Improved REST API to merge settings with defaults and existing values, and to sanitize and save all profile tab-related fields. Adjusted frontend JS and wizard defaults to reflect all tabs, and fixed minor issues with sort option selection and profile slug decoding.
done @Rubaiyat-E-Mohammad vai |
There was a problem hiding this comment.
Actionable comments posted: 4
♻️ Duplicate comments (5)
assets/js/ud-search-shortcode.js (1)
10-14: ShareddebounceTimeoutvariable causes cross-listing interference.This issue was previously identified: the module-scoped
debounceTimeoutat line 14 will interfere when multiple directory instances exist on the same page. Each listing's debounce timer will cancel others'.modules/user-directory/Shortcode.php (2)
498-510: Remove unused private methodget_current_page().This method is dead code as previously identified. The directory rendering uses
$_GET['udpage']directly at line 266, not this method.Based on static analysis.
535-555: Potential URL encoding issue with special characters in usernames.As previously identified, when
profile_baseis 'username', theuser_loginis used directly in the URL without encoding at line 542. Usernames with special characters could produce malformed URLs.modules/user-directory/Api/Directory.php (2)
95-106: Public search endpoint lacks rate limiting.As previously identified, the
/searchendpoint at line 103 uses__return_truefor permissions (necessary for frontend AJAX) but has no rate limiting mechanism. This could enable request flooding or data scraping.
291-292: Avoidflush_rewrite_rules()on every directory creation.As previously flagged, calling
flush_rewrite_rules()on every directory creation is expensive and can cause performance issues.
🧹 Nitpick comments (9)
src/js/user-directory/components/steps/StepTabs.js (2)
33-64: Consider extracting tab initialization logic.The
useStateinitialization forallTabscontains complex logic that could be extracted into a separate helper function for improved readability and testability.💡 Example refactor
+const initializeTabs = (defaultTabs, formData) => { + const savedOrder = Array.isArray(formData.profile_tabs_order) ? formData.profile_tabs_order : []; + const enabledTabs = Array.isArray(formData.profile_tabs) ? formData.profile_tabs + : (formData.profile_tabs && typeof formData.profile_tabs === 'object') + ? Object.keys(formData.profile_tabs).filter(key => formData.profile_tabs[key]) + : ['about']; + + const tabsMap = {}; + Object.keys(defaultTabs).forEach(key => { + tabsMap[key] = { + key, + label: typeof defaultTabs[key] === 'string' ? defaultTabs[key] : defaultTabs[key]?.label || key, + enabled: enabledTabs.includes(key) + }; + }); + + const orderedTabs = []; + if (savedOrder.length > 0) { + savedOrder.forEach(key => { + if (tabsMap[key]) { + orderedTabs.push(tabsMap[key]); + delete tabsMap[key]; + } + }); + } + Object.values(tabsMap).forEach(tab => orderedTabs.push(tab)); + + return orderedTabs; +}; + const StepTabs = ({ formData, setFormData, config }) => { const defaultTabs = config?.profile_tabs || { /* ... */ }; - const [allTabs, setAllTabs] = useState(() => { - const savedOrder = getOrderArray(formData.profile_tabs_order); - // ... (30+ lines of logic) - return orderedTabs; - }); + const [allTabs, setAllTabs] = useState(() => initializeTabs(defaultTabs, formData));
339-339: Consider responsive width for small screens.The fixed width of
590pxmay cause horizontal overflow on smaller screens. Consider usingmax-widthor responsive width utilities to ensure better mobile experience.🔎 Suggested improvement
- <div className="wpuf-mt-4 wpuf-p-4 wpuf-bg-white wpuf-border wpuf-border-gray-200 wpuf-rounded-lg" style={{ width: '590px' }}> + <div className="wpuf-mt-4 wpuf-p-4 wpuf-bg-white wpuf-border wpuf-border-gray-200 wpuf-rounded-lg wpuf-w-full" style={{ maxWidth: '590px' }}>assets/js/ud-search-shortcode.js (1)
172-183: Hard-coded grid structure creates maintenance coupling with PHP template.The innerHTML at line 179 duplicates the exact grid structure from
layout-3.phpwith specific Tailwind classes. If the PHP template's structure changes, this JavaScript will break or create mismatched layouts when restoring the list container after "no results."Consider storing the original container HTML on initialization or fetching the structure from a data attribute to reduce coupling.
🔎 Alternative approach
Store the original list structure on initialization:
function initUserDirectorySearch(container, blockId, pageId) { let currentRequestId = 0; + let originalListHTML = null; const listingDiv = document.querySelector('.wpuf-user-listing[data-block-id="' + blockId + '"]'); const input = listingDiv ? listingDiv.querySelector('.wpuf-ud-search-input') : null; let userList = listingDiv ? ( listingDiv.querySelector('.wpuf-ud-tbody') || listingDiv.querySelector('ul[role="list"]') ) : null; + // Store original structure for restoration + if (listingDiv && userList) { + const listContainer = listingDiv.querySelector('.wpuf-ud-list'); + if (listContainer) { + originalListHTML = listContainer.innerHTML; + } + }Then use it when restoring:
- listContainer.innerHTML = '<div><ul role="list" class="!wpuf-mx-auto !wpuf-grid !wpuf-max-w-2xl !wpuf-grid-cols-1 !wpuf-gap-x-6 !wpuf-gap-y-6 sm:!wpuf-grid-cols-2 lg:!wpuf-mx-0 lg:!wpuf-max-w-none lg:!wpuf-grid-cols-3"></ul></div>'; + if (originalListHTML) { + listContainer.innerHTML = originalListHTML; + }src/js/user-directory/components/steps/StepAdvanced.js (6)
51-55: Consider providing user feedback when Pro features are clicked.When a user clicks a Pro-only avatar size, the change is silently ignored. While this is intentional for feature gating, it provides no feedback to the user, who might think the UI is broken.
Since the Pro badge appears on hover, consider adding a visual indication (e.g., a brief tooltip or alert) when a Pro feature is clicked, or ensure the Pro badge remains visible long enough to convey that the feature requires an upgrade.
67-94: Move style objects outside the component to avoid recreation on every render.The
inputStyleobject is recreated on every render. Since it doesn't depend on props or state, move it outside the component definition to improve performance.🔎 Proposed refactor
Move both
inputStyleandnumberInputStyle(lines 67-113) above the component definition:+// Common input style for select dropdowns +const inputStyle = { + maxWidth: '793px', + width: '100%', + // ... rest of styles +}; + +// Number input style +const numberInputStyle = { + maxWidth: '793px', + // ... rest of styles +}; + const StepAdvanced = ({ formData, setFormData, config }) => { const [hoveredOption, setHoveredOption] = useState(null); - - // Common input style for select dropdowns - const inputStyle = { - maxWidth: '793px', - // ... (remove from here) - };
118-118: Ensure asset URL fallback doesn't result in broken image paths.If both
config?.asset_urlandwindow.wpuf_ud_free?.asset_urlare undefined, the empty string fallback will produce an invalid imagesrclike'/images/pro-badge.svg', which may fail to load.Consider providing a more robust fallback or logging a warning when the asset URL is missing.
🔎 Proposed fix
-src={(config?.asset_url || window.wpuf_ud_free?.asset_url || '') + '/images/pro-badge.svg'} +src={(config?.asset_url || window.wpuf_ud_free?.asset_url || 'data:image/svg+xml,<svg/>') + (config?.asset_url || window.wpuf_ud_free?.asset_url ? '/images/pro-badge.svg' : '')}Or better, validate at the top of the component:
const StepAdvanced = ({ formData, setFormData, config }) => { + const assetUrl = config?.asset_url || window.wpuf_ud_free?.asset_url || ''; + if (!assetUrl) { + console.warn('StepAdvanced: asset_url is not defined'); + } const [hoveredOption, setHoveredOption] = useState(null);Then use:
-src={(config?.asset_url || window.wpuf_ud_free?.asset_url || '') + '/images/pro-badge.svg'} +src={assetUrl + '/images/pro-badge.svg'}
124-164: Consider consistent array structure across all option arrays.The
socialProfilesandprofileBasesarrays lack theisFreeproperty thatsortOptions,avatarSizes, andgallerySizeshave. While the code handles this correctly, maintaining a consistent structure would improve maintainability and prevent potential bugs if the code is refactored to iterate over these arrays generically.Additionally, these arrays are recreated on every render. Consider moving them outside the component definition.
217-229: Ensure consistency between tooltip and placeholder text.The tooltip on Line 217 states "Enter -1 or leave empty to show all users", while the placeholder on Line 229 only mentions "Enter -1 for all users". This inconsistency might confuse users about whether leaving the field empty is a valid option.
Consider updating the placeholder to match the tooltip:
🔎 Proposed fix
-placeholder={__('Enter -1 for all users', 'wp-user-frontend')} +placeholder={__('Enter -1 or leave empty for all users', 'wp-user-frontend')}
241-282: Consider extracting the Pro-gated button pattern into a reusable component.The pattern of hover state management, Pro badge overlay, and disabled styling for Pro features is repeated across multiple sections (sort options, gallery sizes, avatar sizes). This duplication increases maintenance burden.
Consider creating a reusable
ProGatedButtonorProGatedOptioncomponent that encapsulates this logic.Example structure:
const ProGatedButton = ({ value, label, isFree, isSelected, onSelect, variant = 'default' // 'default' or 'card' }) => { const [isHovered, setIsHovered] = useState(false); // ... shared logic };This would reduce code duplication and make the component easier to maintain.
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (7)
assets/js/ud-search-shortcode.jsmodules/user-directory/Api/Directory.phpmodules/user-directory/Shortcode.phpmodules/user-directory/User_Directory.phpsrc/js/user-directory/components/DirectoryWizard.jssrc/js/user-directory/components/steps/StepAdvanced.jssrc/js/user-directory/components/steps/StepTabs.js
🚧 Files skipped from review as they are similar to previous changes (2)
- src/js/user-directory/components/DirectoryWizard.js
- modules/user-directory/User_Directory.php
🧰 Additional context used
🧬 Code graph analysis (3)
src/js/user-directory/components/steps/StepTabs.js (2)
src/js/user-directory/App.js (1)
config(36-36)assets/js/wpuf-user-directory-free.js (1)
e(2-2)
assets/js/ud-search-shortcode.js (1)
src/js/user-directory/index.js (1)
container(17-17)
modules/user-directory/Api/Directory.php (2)
modules/user-directory/User_Directory.php (3)
User_Directory(24-317)has_reached_limit(206-214)get_default_settings(286-316)wpuf-functions.php (1)
wpuf_is_pro_active(5045-5047)
🪛 PHPMD (2.15.0)
modules/user-directory/Shortcode.php
498-510: Avoid unused private methods such as 'get_current_page'. (undefined)
(UnusedPrivateMethod)
modules/user-directory/Api/Directory.php
131-131: Avoid unused parameters such as '$request'. (undefined)
(UnusedFormalParameter)
526-526: Avoid unused local variables such as '$user'. (undefined)
(UnusedLocalVariable)
528-528: Avoid unused local variables such as '$all_data'. (undefined)
(UnusedLocalVariable)
547-547: Avoid unused local variables such as '$pagination'. (undefined)
(UnusedLocalVariable)
563-563: Avoid unused local variables such as '$layout'. (undefined)
(UnusedLocalVariable)
🔇 Additional comments (1)
src/js/user-directory/components/steps/StepAdvanced.js (1)
1-581: Overall assessment: Well-implemented Pro/free gating UI with minor optimization opportunities.This component successfully implements a comprehensive Advanced Control step with clear Pro/free feature differentiation. The hover-based Pro badge indicators provide good visual feedback, and the feature gating is consistently applied throughout.
Key strengths:
- Consistent Pro/free gating logic across all features
- Good use of visual indicators (hover states, Pro badges, disabled styles)
- Comprehensive form field coverage
- Proper accessibility considerations (sr-only classes, aria-friendly markup)
Areas for improvement (addressed in specific comments):
- Performance optimizations (style objects, array definitions)
- Code reusability (extracted components for repeated patterns)
- Minor UX enhancements (feedback on Pro feature clicks)
- Consistency checks (tooltip/placeholder text, case handling)
There was a problem hiding this comment.
Actionable comments posted: 6
🤖 Fix all issues with AI agents
In `@modules/user-directory/Shortcode.php`:
- Around line 286-333: The per_page computed by $per_page = absint(
$settings['users_per_page'] ?? ( $settings['per_page'] ?? 12 ) ) can be 0,
causing a division-by-zero in the max_pages calculation and producing a
WP_User_Query with 'number' => 0; add a guard right after computing $per_page to
coerce any 0 value to a sane minimum (e.g. 1) and ensure $args['number'] uses
that sanitized $per_page before running new \WP_User_Query and calculating
$max_pages.
- Around line 413-420: In render_profile, stop reading the non-existent
$settings['default_tabs'] and instead read $settings['profile_tabs'] (keep the
same array/string handling: accept array or CSV string and normalize via
array_map/explode/trim) so the configured profile tabs are used; and in
User_Directory::get_default_settings() change the default tab identifier from
'files' to 'file' (so the default tabs and JS/template checks like
in_array('file', $default_tabs) and pagination config remain consistent).
In `@src/js/user-directory/App.js`:
- Around line 56-84: fetchDirectories currently calls response.json() without
checking response.ok; add a guard that verifies response.ok before parsing to
avoid processing error responses. Inside fetchDirectories, after awaiting
fetch(url,...), check if (!response.ok) then log the response status/statusText,
setDirectories([]) and setTotalPages(1) (and return or skip parsing); only call
response.json() and process data when response.ok is true. Keep references to
fetchDirectories, setDirectories, setTotalPages and the response handling so the
behavior mirrors the delete handler's response.ok check.
In `@src/js/user-directory/components/common/SingleSelect.js`:
- Around line 103-135: The grouped rendering currently outputs option items as
<div> inside a <ul>/<li> list which is invalid HTML; in the SingleSelect
component change the inner option render (inside
Object.entries(groupedOptions).map -> groupOptions.map) to render each option as
an <li> (preserve the key using option.value), keep the same className, onClick
handler (handleSelect(option)), selection logic (isSelected/isFree) and the
ProBadge placement, and ensure any existing structure/content (span for label
and ProBadge) is moved inside that <li> so semantics match the flat rendering
path and nothing else in the component (value, handleSelect, ProBadge) needs to
change.
In `@src/js/user-directory/components/steps/StepAdvanced.js`:
- Around line 146-154: Update the mistaken avatar size value 265 to 256 wherever
it appears: change the element in the avatarSizes array (the object with value
'265' and label '265×265') to use '256' and '256×256', and make the equivalent
update in the avatarSizeHelper module (replace any '265' occurrences used for
available sizes or labels). Ensure both the avatarSizes constant in
StepAdvanced.js and the corresponding size list/logic in avatarSizeHelper.js
consistently use '256' so UI labels and value strings match.
In `@src/js/user-directory/components/steps/StepTabs.js`:
- Around line 80-84: The forEach callback in StepTabs.js is implicitly returning
the result of orderedTabs.push(tab) (because of the arrow shorthand); change the
callback to use a block body or use an explicit loop so it does not return a
value — e.g., replace Object.values(tabsMap).forEach(tab =>
orderedTabs.push(tab)); with a non-returning form (for example:
Object.values(tabsMap).forEach(tab => { orderedTabs.push(tab); }); or a for...of
loop) in the function that builds orderedTabs to satisfy the static analysis
rule.
🧹 Nitpick comments (10)
modules/user-directory/Shortcode.php (2)
449-449: Self-referencing snapshot in$profile_data.
$profile_data['template_data'] = $profile_datacopies the full array into itself for "Pro template compatibility." Because PHP arrays are copy-on-write this won't loop, but it roughly doubles the memory footprint of the data bag for every profile render. Consider assigning only the subset the template actually needs, or using a reference if the template truly expects the full bag.
468-489:extract()usage is acceptable here but worth noting.
extract($data)can silently overwrite local variables (including$template_fileand$templatefrom the outer scope). Since the template data keys are internally controlled, this is low-risk, but a collision with$template_fileor$datawould cause subtle bugs.If you ever expose filter hooks that let third parties inject arbitrary keys into
$data, consider usingEXTR_SKIPto prevent overwrites:- extract( $data ); + extract( $data, EXTR_SKIP );src/js/user-directory/components/steps/StepProfile.js (3)
103-123: ProBadge is duplicated across multiple files — consider extracting a shared component.This file constructs its own inline Pro badge markup (lines 106-118). The same pattern appears in
App.js(line 17),DirectoryList.js(line 12), andStepTabs.js(line 5), each with slight variations in props and asset URL resolution logic.A single shared
ProBadgecomponent (e.g., incomponents/common/ProBadge.js) acceptingutm,width,heightprops would eliminate the duplication and ensure consistent upgrade URL construction across all files.
4-4: Unusedconfigprop.
configis destructured from props but never referenced in the component body. Either remove it from the signature or use it (e.g., for the asset URL resolution on line 114 instead of accessingwindowglobals directly).
56-56: Fixed-width grid items may overflow on narrow viewports.Each label has
width: '255px'(line 69) in a 3-column grid. On viewports below ~800px, this will overflow horizontally. If the admin UI is expected to render in narrower containers (e.g., WordPress admin on tablets), consider using responsive widths ormax-widthinstead.src/js/user-directory/components/common/SingleSelect.js (1)
66-163: Missing keyboard navigation for accessibility.The dropdown lacks keyboard handlers — no support for
Escapeto close,Enter/Spaceto select, orArrowUp/ArrowDownto navigate options. Keyboard-only users cannot interact with this control. Consider addingonKeyDownon the dropdown list items and the trigger button, and managing focus withtabIndexandaria-activedescendant.src/js/user-directory/components/steps/StepTabs.js (1)
398-398: Fixed width590pxon content type preview may overflow narrow containers.The content type buttons container has a hardcoded
width: '590px'. Consider usingmax-widthinstead, or a responsive class, to avoid horizontal overflow in narrower admin contexts.Proposed fix
- <div className="wpuf-mt-4 wpuf-p-4 wpuf-bg-white wpuf-border wpuf-border-gray-200 wpuf-rounded-lg" style={{ width: '590px' }}> + <div className="wpuf-mt-4 wpuf-p-4 wpuf-bg-white wpuf-border wpuf-border-gray-200 wpuf-rounded-lg" style={{ maxWidth: '590px', width: '100%' }}>src/js/user-directory/App.js (2)
52-54:fetchDirectoriesis a missing dependency in theuseEffecthook.React's rules of hooks require all referenced functions/values to be listed in the dependency array.
fetchDirectoriesis called inside the effect but not listed. This won't cause a bug here sincefetchDirectoriesreceivescurrentPageas an argument, but it will trigger ESLint'sreact-hooks/exhaustive-depswarning. WrappingfetchDirectoriesinuseCallbackor inlining the fetch in the effect would silence it.
111-139: Error handling usesalert()— consider a toast notification.The PR summary mentions toast support in the admin UI. Using
alert()on lines 123, 132, and 135 for error feedback blocks the UI and is inconsistent with the rest of the admin experience. Consider using the toast system if available.src/js/user-directory/components/steps/StepAdvanced.js (1)
23-38:parseIntcalled without explicit radix.Line 29 uses
parseInt(value)without a radix argument. While modern JS defaults to base 10 for decimal strings, passing the radix explicitly (parseInt(value, 10)) is a best practice to avoid edge cases and satisfies common lint rules.Proposed fix
- numericValue = parseInt(value); + numericValue = parseInt(value, 10);
| private function render_profile( $user, $settings, $directory_id ) { | ||
| // Get default tabs from settings or use Free version defaults | ||
| $default_tabs = [ 'about', 'posts', 'comments', 'file' ]; | ||
| if ( ! empty( $settings['default_tabs'] ) && is_array( $settings['default_tabs'] ) ) { | ||
| $default_tabs = $settings['default_tabs']; | ||
| } elseif ( ! empty( $settings['default_tabs'] ) && is_string( $settings['default_tabs'] ) ) { | ||
| $default_tabs = array_map( 'trim', explode( ',', $settings['default_tabs'] ) ); | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, locate the User_Directory class and find get_default_settings
find . -type f -name "*.php" | xargs grep -l "class User_Directory" | head -5Repository: weDevsOfficial/wp-user-frontend
Length of output: 118
🏁 Script executed:
# Search for get_default_settings method to see what keys are actually defined
rg -n "get_default_settings|default_tabs|profile_tabs" --type=php -A 5 -B 2 | head -100Repository: weDevsOfficial/wp-user-frontend
Length of output: 9330
🏁 Script executed:
# Check the user-directory module structure
find . -type f -name "*.php" -path "*/user-directory/*" | head -20Repository: weDevsOfficial/wp-user-frontend
Length of output: 1183
🏁 Script executed:
# Search for where these settings are used in templates or rendering
rg -n "'file'|'files'" --type=php -g "*user-directory*" -B 2 -A 2Repository: weDevsOfficial/wp-user-frontend
Length of output: 686
🏁 Script executed:
# Search for all tab identifier references in templates
rg -n "in_array.*\$default_tabs|'about'|'posts'|'comments'|'activity'|'message'" modules/user-directory/views/profile/ --type=php -B 1 -A 1Repository: weDevsOfficial/wp-user-frontend
Length of output: 5218
🏁 Script executed:
# Check if there are other layout files that might use different tab identifiers
fd "layout" modules/user-directory/views/profile/ --type fRepository: weDevsOfficial/wp-user-frontend
Length of output: 124
🏁 Script executed:
# Check the Shortcode.php more carefully to understand the full context
sed -n '410,425p' modules/user-directory/Shortcode.phpRepository: weDevsOfficial/wp-user-frontend
Length of output: 835
🏁 Script executed:
# Check Helpers.php and other files for any tab identifier references
rg -n "profile_tabs|default_tabs" modules/user-directory/ --type=php -B 2 -A 2 | head -60Repository: weDevsOfficial/wp-user-frontend
Length of output: 5729
🏁 Script executed:
# Check if there are settings stored/retrieved elsewhere
rg -n "get_option.*directory|update_option.*directory" modules/user-directory/ --type=phpRepository: weDevsOfficial/wp-user-frontend
Length of output: 57
Fix settings key mismatch and correct tab identifier inconsistency.
Two issues here, but the proposed fix will break the Files tab:
-
Wrong settings key: The code reads
$settings['default_tabs'](lines 416-419), butUser_Directory::get_default_settings()defines the key asprofile_tabs(line 303). Sincedefault_tabsis never populated by the API, the hardcoded fallback on line 415 is always used. -
Critical flaw in proposed fix: The default settings define
'files'(plural) on line 303, but the template checks for'file'(singular) on line 265, and the JavaScript pagination config uses'file'on line 334. The proposed diff changes the hardcoded fallback to'files', which would cause the template'sin_array( 'file', $default_tabs )check to fail, breaking the Files tab entirely.
Correct fix: Change the settings key from 'default_tabs' to 'profile_tabs', AND change the default value in User_Directory::get_default_settings() from 'files' to 'file' to match the template and JavaScript expectations.
Corrected fix
- $default_tabs = [ 'about', 'posts', 'comments', 'file' ];
- if ( ! empty( $settings['default_tabs'] ) && is_array( $settings['default_tabs'] ) ) {
- $default_tabs = $settings['default_tabs'];
- } elseif ( ! empty( $settings['default_tabs'] ) && is_string( $settings['default_tabs'] ) ) {
- $default_tabs = array_map( 'trim', explode( ',', $settings['default_tabs'] ) );
+ $default_tabs = [ 'about', 'posts', 'comments', 'file' ];
+ if ( ! empty( $settings['profile_tabs'] ) && is_array( $settings['profile_tabs'] ) ) {
+ $default_tabs = $settings['profile_tabs'];
+ } elseif ( ! empty( $settings['profile_tabs'] ) && is_string( $settings['profile_tabs'] ) ) {
+ $default_tabs = array_map( 'trim', explode( ',', $settings['profile_tabs'] ) );
}Additionally, update User_Directory.php line 303:
- 'profile_tabs' => [ 'about', 'posts', 'files', 'comments' ],
+ 'profile_tabs' => [ 'about', 'posts', 'file', 'comments' ],📝 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 render_profile( $user, $settings, $directory_id ) { | |
| // Get default tabs from settings or use Free version defaults | |
| $default_tabs = [ 'about', 'posts', 'comments', 'file' ]; | |
| if ( ! empty( $settings['default_tabs'] ) && is_array( $settings['default_tabs'] ) ) { | |
| $default_tabs = $settings['default_tabs']; | |
| } elseif ( ! empty( $settings['default_tabs'] ) && is_string( $settings['default_tabs'] ) ) { | |
| $default_tabs = array_map( 'trim', explode( ',', $settings['default_tabs'] ) ); | |
| } | |
| private function render_profile( $user, $settings, $directory_id ) { | |
| // Get default tabs from settings or use Free version defaults | |
| $default_tabs = [ 'about', 'posts', 'comments', 'file' ]; | |
| if ( ! empty( $settings['profile_tabs'] ) && is_array( $settings['profile_tabs'] ) ) { | |
| $default_tabs = $settings['profile_tabs']; | |
| } elseif ( ! empty( $settings['profile_tabs'] ) && is_string( $settings['profile_tabs'] ) ) { | |
| $default_tabs = array_map( 'trim', explode( ',', $settings['profile_tabs'] ) ); | |
| } |
🤖 Prompt for AI Agents
In `@modules/user-directory/Shortcode.php` around lines 413 - 420, In
render_profile, stop reading the non-existent $settings['default_tabs'] and
instead read $settings['profile_tabs'] (keep the same array/string handling:
accept array or CSV string and normalize via array_map/explode/trim) so the
configured profile tabs are used; and in User_Directory::get_default_settings()
change the default tab identifier from 'files' to 'file' (so the default tabs
and JS/template checks like in_array('file', $default_tabs) and pagination
config remain consistent).
src/js/user-directory/App.js
Outdated
| const fetchDirectories = async (page = 1) => { | ||
| setLoading(true); | ||
| try { | ||
| const restUrl = config.rest_url || '/wp-json/'; | ||
| const url = `${restUrl}wpuf/v1/user_directory?page=${page}&per_page=${perPage}`; | ||
| const response = await fetch(url, { | ||
| headers: { | ||
| 'X-WP-Nonce': config.rest_nonce || '', | ||
| }, | ||
| }); | ||
| const data = await response.json(); | ||
| if (data && data.success && Array.isArray(data.result)) { | ||
| setDirectories(data.result); | ||
| if (data.pagination && data.pagination.total_pages) { | ||
| setTotalPages(data.pagination.total_pages); | ||
| } else { | ||
| setTotalPages(1); | ||
| } | ||
| } else { | ||
| setDirectories([]); | ||
| setTotalPages(1); | ||
| } | ||
| } catch (error) { | ||
| console.error('Error fetching directories:', error); | ||
| setDirectories([]); | ||
| setTotalPages(1); | ||
| } | ||
| setLoading(false); | ||
| }; |
There was a problem hiding this comment.
Missing response.ok check before parsing JSON in fetchDirectories.
On line 66, response.json() is called unconditionally. If the server returns a non-2xx status (e.g., 403, 500), this may parse an error response or throw. The delete handler (line 122) correctly checks response.ok first — apply the same pattern here.
Proposed fix
const response = await fetch(url, {
headers: {
'X-WP-Nonce': config.rest_nonce || '',
},
});
+ if (!response.ok) {
+ throw new Error(`Server error: ${response.status}`);
+ }
const data = await response.json();🤖 Prompt for AI Agents
In `@src/js/user-directory/App.js` around lines 56 - 84, fetchDirectories
currently calls response.json() without checking response.ok; add a guard that
verifies response.ok before parsing to avoid processing error responses. Inside
fetchDirectories, after awaiting fetch(url,...), check if (!response.ok) then
log the response status/statusText, setDirectories([]) and setTotalPages(1) (and
return or skip parsing); only call response.json() and process data when
response.ok is true. Keep references to fetchDirectories, setDirectories,
setTotalPages and the response handling so the behavior mirrors the delete
handler's response.ok check.
| const avatarSizes = [ | ||
| { value: '32', label: '32×32', isFree: false }, | ||
| { value: '48', label: '48×48', isFree: false }, | ||
| { value: '80', label: '80×80', isFree: false }, | ||
| { value: '128', label: '128×128', isFree: true }, | ||
| { value: '160', label: '160×160', isFree: false }, | ||
| { value: '192', label: '192×192', isFree: false }, | ||
| { value: '265', label: '265×265', isFree: false }, | ||
| ]; |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Search for avatar size 265 references
echo "=== Searching for 265 avatar size references ==="
rg '265' -t js -t php -B 2 -A 2 | head -50
echo ""
echo "=== Checking for Pro/Premium avatar sizes ==="
rg -i 'avatar.*size|size.*avatar' -t js -t php -B 2 -A 2 | grep -E '256|265|192|160' | head -40
echo ""
echo "=== Searching for avatar size definitions ==="
rg 'avatarSizes|avatar.*sizes' -t js -B 1 -A 10 | head -60Repository: weDevsOfficial/wp-user-frontend
Length of output: 50389
🏁 Script executed:
#!/bin/bash
# Search more specifically for avatar size definitions and Pro version context
echo "=== Searching for 256 (power of 2) vs 265 ==="
rg '256|265' -t js --max-count=20
echo ""
echo "=== Full context of avatarSizeHelper.js ==="
cat src/js/user-directory/utils/avatarSizeHelper.js 2>/dev/null || echo "File not found"
echo ""
echo "=== Full context of avatar sizes in StepAdvanced.js (lines 140-160) ==="
sed -n '140,160p' src/js/user-directory/components/steps/StepAdvanced.js 2>/dev/null || echo "File not found"
echo ""
echo "=== Search for Pro or Premium references with 265 ==="
rg -i 'pro|premium|paid' -B 5 -A 5 '265' -t js | head -40Repository: weDevsOfficial/wp-user-frontend
Length of output: 50450
🏁 Script executed:
#!/bin/bash
# Read the full avatarSizeHelper.js file
echo "=== Full avatarSizeHelper.js ==="
cat src/js/user-directory/utils/avatarSizeHelper.js 2>/dev/null
echo ""
echo "=== Context around StepAdvanced.js line 146-154 ==="
sed -n '140,160p' src/js/user-directory/components/steps/StepAdvanced.js 2>/dev/null
echo ""
echo "=== Search for any comments or documentation about avatar sizes ==="
rg -i 'avatar|size' -B 2 -A 2 src/js/user-directory/utils/avatarSizeHelper.js src/js/user-directory/components/steps/StepAdvanced.js 2>/dev/null | head -80Repository: weDevsOfficial/wp-user-frontend
Length of output: 9273
Change avatar size 265 to 256 — this appears to be a typo. The value 265 lacks justification and is not a standard image dimension, while 256 (2⁸) follows the expected power-of-two pattern for sizes like 32, 64, 128, etc. Update both occurrences in src/js/user-directory/components/steps/StepAdvanced.js and src/js/user-directory/utils/avatarSizeHelper.js.
🤖 Prompt for AI Agents
In `@src/js/user-directory/components/steps/StepAdvanced.js` around lines 146 -
154, Update the mistaken avatar size value 265 to 256 wherever it appears:
change the element in the avatarSizes array (the object with value '265' and
label '265×265') to use '256' and '256×256', and make the equivalent update in
the avatarSizeHelper module (replace any '265' occurrences used for available
sizes or labels). Ensure both the avatarSizes constant in StepAdvanced.js and
the corresponding size list/logic in avatarSizeHelper.js consistently use '256'
so UI labels and value strings match.
Replace the native <select> for the profile_base field with a SingleSelect component, passing profileBases as options and adapting the onChange to call the existing handleChange ({ target: { name, value } }). Remove the now-unused inputStyle constant and its comment block. This simplifies the markup and delegates select rendering/styling to the SingleSelect component.
|
Safelist new button and focus utilities in the user-directory Tailwind config and update DirectoryWizard and StepTabs components. DirectoryWizard: replace verbose button classnames with wpuf-btn-white, add focus ring utilities to Cancel/Prev/Next/Next (submit) buttons for better accessibility, adjust the footer container style to ensure correct left/right positioning and enforce background color. StepTabs: only render the ProBadge on hover to reduce visual clutter. These changes centralize button styles and improve keyboard focus visibility and layout consistency.
|
done @Rubaiyat-E-Mohammad vai |
There was a problem hiding this comment.
Actionable comments posted: 8
🤖 Fix all issues with AI agents
In `@modules/user-directory/tailwind.config.js`:
- Around line 9-13: The Tailwind config's content array currently only scans
'./views/**/*.php' so utility classes in Admin_Menu.php (e.g., wpuf-h-100vh,
wpuf-bg-white, wpuf-ml-[-20px], !wpuf-py-0, wpuf-px-[20px]) are purged; fix by
either adding './Admin_Menu.php' to the content array in
modules/user-directory/tailwind.config.js or, if you prefer to keep content
globs, add those exact class names (and any variants like the !important prefix)
to the safelist in the same file so Admin_Menu.php styles are preserved in
production.
- Around line 1-7: The config mixes ESM import with CommonJS export; replace the
ES import of scopedPreflightStyles and isolateInsideOfContainer with CommonJS
requires so the file uses a single module system: change the top-level "import {
scopedPreflightStyles, isolateInsideOfContainer } from
'tailwindcss-scoped-preflight'" to equivalent const require(...) assignments and
keep the existing module.exports and plugins usage (referencing
scopedPreflightStyles and isolateInsideOfContainer) so all symbols are loaded
via require().
In `@src/js/user-directory/components/DirectoryWizard.js`:
- Around line 131-134: The error handling for failed fetch responses in
DirectoryWizard.js currently calls response.json() directly which can throw on
non-JSON responses; wrap the call to response.json() in a try/catch (inside the
existing if (!response.ok) branch), and if parsing fails use a fallback message
such as response.statusText or a localized generic string before throwing;
update the thrown Error to include either the parsed data.message (when
available) or the fallback text so the code paths in the function that calls
this fetch (the async handler using response) get a meaningful error instead of
a JSON parse exception.
- Around line 86-99: handleNextStep captures currentStepIndex in the timeout
closure which can be stale; change the state update to a functional updater and
set prevStepIndex.current inside that updater so the latest value is used:
inside the setTimeout replace setCurrentStepIndex(currentStepIndex + 1) and
prevStepIndex.current = currentStepIndex with a single setCurrentStepIndex(prev
=> { prevStepIndex.current = prev; return prev + 1; }); apply the same pattern
(functional updater or a ref-read) to handlePrevStep and navigateToStep to avoid
stale-closure bugs.
- Around line 269-273: The footer's inline style reads
document.body.classList.contains('folded') directly in DirectoryWizard's render,
which is non-reactive and causes misalignment when the WP sidebar toggles;
update the component (DirectoryWizard) to track the folded state in React state
via a custom hook or useEffect + MutationObserver (or a window/DOM event
listener) that watches document.body.classList for the 'folded' class and sets a
state like isFolded, then use that state to compute the left style instead of
direct DOM reads; ensure you clean up the observer/listener on unmount and
reference the updated isFolded state where the inline style is currently set.
In `@src/js/user-directory/components/steps/StepTabs.js`:
- Line 400: In the StepTabs component update the div that currently uses
style={{ width: '590px' }} to use responsive sizing instead (e.g., width: '100%'
plus a maxWidth of 590px or a Tailwind utility like "w-full max-w-[590px]") so
it no longer forces a fixed pixel width; locate the div in StepTabs.js (the
container with className "wpuf-mt-4 wpuf-p-4 wpuf-bg-white wpuf-border
wpuf-border-gray-200 wpuf-rounded-lg") and replace the fixed width style with a
responsive approach (maxWidth + width: '100%' or equivalent Tailwind classes).
- Around line 418-423: The overlay div that renders ProBadge when hovered (check
the conditional rendering tied to hoveredContentType in StepTabs.js) uses
style={{ pointerEvents: 'none' }}, which prevents the anchor inside ProBadge
from receiving clicks; remove pointerEvents: 'none' from these overlay divs (or
set pointerEvents: 'auto' on the ProBadge container) so the link inside ProBadge
is clickable — update every instance where the overlay is rendered for the five
content-type buttons adjacent to ProBadge to ensure the badge's internal <a> can
receive pointer events.
- Around line 342-352: In StepTabs (component StepTabs.js) the heading element
rendering "Configure About Tab" is currently an <h1> which breaks the document
outline after an earlier <h2> ("Profile Content"); replace the <h1> used for the
"Configure About Tab" heading with a semantically correct lower-level heading
(e.g., <h3>, or at minimum <h2>) so the heading hierarchy is preserved and
accessibility/SEO are corrected; update the JSX where the string __('Configure
About Tab', 'wp-user-frontend') is rendered to use the chosen heading tag.
🧹 Nitpick comments (7)
src/js/user-directory/components/steps/StepTabs.js (2)
401-521: Extract a reusable component for the five nearly identical content-type buttons.Each content-type button (Meta Field, Section, Post Type, Comment, Image/File) repeats the same markup, classes, hover logic, and overlay pattern — differing only in icon SVG, label, and UTM string. This is a significant DRY violation that makes future style changes error-prone.
Suggested extraction
const ContentTypeButton = ({ icon, label, typeKey, utm, hoveredContentType, setHoveredContentType }) => ( <button type="button" className="wpuf-relative wpuf-flex wpuf-flex-col wpuf-items-center wpuf-justify-center wpuf-rounded-lg wpuf-text-sm wpuf-font-medium wpuf-w-[102px] wpuf-gap-1 wpuf-p-3 wpuf-pb-1 wpuf-bg-gray-50 wpuf-text-gray-400 wpuf-cursor-not-allowed wpuf-opacity-60 hover:wpuf-opacity-80" style={{ border: '1px solid `#E5E7EB`' }} onMouseEnter={() => setHoveredContentType(typeKey)} onMouseLeave={() => setHoveredContentType(null)} > {icon} <span className="wpuf-text-sm wpuf-font-normal wpuf-leading-5 wpuf-text-gray-400"> {label} </span> {hoveredContentType === typeKey && ( <div className="wpuf-absolute wpuf-inset-0 wpuf-flex wpuf-items-center wpuf-justify-center wpuf-bg-white/90 wpuf-rounded-lg"> <ProBadge utm={utm} /> </div> )} </button> );Then replace each button with a
<ContentTypeButton ... />call.
5-23:ProBadgeis duplicated across at least four files with minor variations.Identical or near-identical
ProBadgecomponents exist inApp.js,StepAdvanced.js,DirectoryList.js, and now here. Consider extracting a single sharedProBadgecomponent undercomponents/common/to avoid drift between copies.src/js/user-directory/components/DirectoryWizard.js (3)
77-110: Duplicated fade-transition logic across three navigation functions.
navigateToStep,handleNextStep, andhandlePrevStepall repeat the samesetFade(false) → setTimeout → update prevStepIndex → setCurrentStepIndex → setFade(true)pattern. Extract a shared helper to reduce duplication.Example helper
+const transitionToStep = (getNextIndex) => { + setFade(false); + setTimeout(() => { + setCurrentStepIndex((prev) => { + prevStepIndex.current = prev; + return getNextIndex(prev); + }); + setFade(true); + }, 200); +}; + const navigateToStep = (stepIndex) => { - setFade(false); - setTimeout(() => { - prevStepIndex.current = currentStepIndex; - setCurrentStepIndex(stepIndex); - setFade(true); - }, 200); + transitionToStep(() => stepIndex); };
148-150:handleSaveAndExitis a redundant wrapper aroundhandleSubmit.It adds no logic beyond awaiting
handleSubmit. You can passhandleSubmitdirectly to the button'sonClickat line 185.
196-254: Step circles always render a checkmark SVG, even for future (incomplete) steps.The white checkmark is only hidden by the green background not being applied for future steps, but on a white circle with white stroke it's invisible. This works visually but is semantically misleading for screen readers and fragile if styles change. Consider conditionally rendering the checkmark only for completed steps, or adding
aria-current="step"for the active step for better accessibility.modules/user-directory/tailwind.config.js (2)
14-84: Duplicate safelist entries and potentially ineffective custom class names.Two issues with the safelist:
Duplicates —
hover:!wpuf-bg-purple-700(lines 42 & 62) andhover:!wpuf-text-purple-600(lines 41 & 65) are listed twice.
wpuf-btn-white/wpuf-btn-primary(lines 76–77) — These are not standard Tailwind utilities, so safelisting them has no effect unless they are defined as custom component classes via@layer componentsin CSS. If they are already defined in CSS, they don't need to be safelisted. If they're expected to be generated by Tailwind, they won't be.🧹 Remove duplicates
'hover:wpuf-text-purple-600', 'hover:wpuf-bg-purple-700', 'hover:wpuf-border-purple-600', 'focus:wpuf-ring-purple-500', - 'hover:!wpuf-text-purple-600', - 'hover:!wpuf-bg-purple-700', 'hover:!wpuf-border-purple-600', 'focus:!wpuf-ring-purple-500',And verify whether
wpuf-btn-white/wpuf-btn-primaryactually need to be in the safelist.
85-92: Consider kebab-case for custom color keys.
primaryHovergenerates classes likewpuf-bg-primaryHover. Tailwind convention is kebab-case (primary-hover→wpuf-bg-primary-hover), which reads more naturally alongside other Tailwind utilities.✏️ Suggested rename
colors: { primary: '#059669', - primaryHover: '#10b981', + 'primary-hover': '#10b981', },
| /** @type {import('tailwindcss').Config} */ | ||
| import { | ||
| scopedPreflightStyles, | ||
| isolateInsideOfContainer, | ||
| } from 'tailwindcss-scoped-preflight'; | ||
|
|
||
| module.exports = { |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# Check package.json for "type": "module" setting
echo "=== Checking package.json type setting ==="
if [ -f package.json ]; then
jq '.type // "not set"' package.json
else
echo "package.json not found"
fi
echo ""
echo "=== Looking for other tailwind configs ==="
fd 'tailwind.config' -t f 2>/dev/null || echo "No tailwind.config files found"
echo ""
echo "=== Looking for other postcss/webpack configs ==="
fd -t f '(postcss|webpack|next\.config)' 2>/dev/null | head -20
echo ""
echo "=== Checking the actual file content (first 20 lines) ==="
cat -n modules/user-directory/tailwind.config.js | head -20Repository: weDevsOfficial/wp-user-frontend
Length of output: 1191
🏁 Script executed:
echo "=== Root tailwind.config.js (first 20 lines) ==="
head -20 tailwind.config.js
echo ""
echo "=== modules/user-directory/postcss.config.js ==="
cat modules/user-directory/postcss.config.js
echo ""
echo "=== modules/user-directory/webpack.config.js (first 30 lines) ==="
head -30 modules/user-directory/webpack.config.js
echo ""
echo "=== Root postcss.config.js ==="
cat postcss.config.jsRepository: weDevsOfficial/wp-user-frontend
Length of output: 1955
🏁 Script executed:
echo "=== Checking for babel config ==="
ls -la | grep -E 'babel|\.babelrc'
find . -maxdepth 2 -name '.babelrc*' -o -name 'babel.config.*' 2>/dev/null
echo ""
echo "=== Checking webpack babel-loader config (from earlier output) ==="
sed -n '20,50p' modules/user-directory/webpack.config.js
echo ""
echo "=== Checking package.json scripts ==="
jq '.scripts' package.json
echo ""
echo "=== Checking how Tailwind is invoked ==="
grep -r 'tailwindcss' package.json 2>/dev/null || echo "Not in package.json"
</parameter>Repository: weDevsOfficial/wp-user-frontend
Length of output: 2176
🏁 Script executed:
echo "=== Looking for Gruntfile ==="
find . -maxdepth 2 -name 'Gruntfile*' -o -name 'grunt.config.*' 2>/dev/null
echo ""
echo "=== Checking Node version requirements ==="
cat package.json | jq '.engines // "not set"'
echo ""
echo "=== Checking if there's a .npmrc or other config ==="
cat .npmrc 2>/dev/null || echo "No .npmrc"
echo ""
echo "=== Looking for any node config files ==="
ls -la | grep -E 'node|npm'Repository: weDevsOfficial/wp-user-frontend
Length of output: 280
🏁 Script executed:
# Find the Gruntfile
if [ -f Gruntfile.js ]; then
echo "=== Gruntfile.js found, checking tailwind task ==="
grep -A 30 "tailwind" Gruntfile.js | head -50
fiRepository: weDevsOfficial/wp-user-frontend
Length of output: 1809
🏁 Script executed:
echo "=== Searching for tailwind task definition in Gruntfile ==="
grep -n "grunt.registerTask\|tailwind:" Gruntfile.js | head -20
echo ""
echo "=== Searching for 'tailwind' task configuration ==="
sed -n '/tailwind.*:{/,/^[[:space:]]*}/p' Gruntfile.js | head -100Repository: weDevsOfficial/wp-user-frontend
Length of output: 1098
🏁 Script executed:
echo "=== Detailed tailwind task implementation (lines 298-365) ==="
sed -n '298,365p' Gruntfile.js
echo ""
echo "=== Looking for shell:tailwind commands ==="
sed -n '/shell.*:/,/^[[:space:]]*}/p' Gruntfile.js | grep -A 5 "tailwind"Repository: weDevsOfficial/wp-user-frontend
Length of output: 3140
🏁 Script executed:
echo "=== Checking for Node version management files ==="
ls -la | grep -E 'nvmrc|node-version|\.tool-versions'
echo ""
echo "=== Checking if modules/user-directory has its own package.json ==="
if [ -f modules/user-directory/package.json ]; then
echo "Found! Checking type field:"
jq '.type // "not set"' modules/user-directory/package.json
echo ""
echo "Full package.json:"
cat modules/user-directory/package.json
fiRepository: weDevsOfficial/wp-user-frontend
Length of output: 1361
Use consistent module syntax throughout the config file.
Lines 2–5 use ES module import, but line 7 uses module.exports. While the root tailwind.config.js uses the same mixed pattern, consolidating to require() throughout maintains consistency and clarity. Convert the import statement to const ... = require(...) to match the CommonJS pattern already used in module.exports and the plugins array.
Suggested refactoring
/** `@type` {import('tailwindcss').Config} */
-import {
- scopedPreflightStyles,
- isolateInsideOfContainer,
-} from 'tailwindcss-scoped-preflight';
+const {
+ scopedPreflightStyles,
+ isolateInsideOfContainer,
+} = require('tailwindcss-scoped-preflight');
module.exports = {📝 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.
| /** @type {import('tailwindcss').Config} */ | |
| import { | |
| scopedPreflightStyles, | |
| isolateInsideOfContainer, | |
| } from 'tailwindcss-scoped-preflight'; | |
| module.exports = { | |
| /** `@type` {import('tailwindcss').Config} */ | |
| const { | |
| scopedPreflightStyles, | |
| isolateInsideOfContainer, | |
| } = require('tailwindcss-scoped-preflight'); | |
| module.exports = { |
🤖 Prompt for AI Agents
In `@modules/user-directory/tailwind.config.js` around lines 1 - 7, The config
mixes ESM import with CommonJS export; replace the ES import of
scopedPreflightStyles and isolateInsideOfContainer with CommonJS requires so the
file uses a single module system: change the top-level "import {
scopedPreflightStyles, isolateInsideOfContainer } from
'tailwindcss-scoped-preflight'" to equivalent const require(...) assignments and
keep the existing module.exports and plugins usage (referencing
scopedPreflightStyles and isolateInsideOfContainer) so all symbols are loaded
via require().
sapayth
left a comment
There was a problem hiding this comment.
Check the comments/suggestions from coderabbitai. also:
-
SQL Injection via unsanitized orderby in REST API
modules/user-directory/Api/Directory.php — The orderby parameter from user input is interpolated directly into SQL:
$orderby_clause = "ORDER BY {$orderby} {$order}";
The orderby value must be validated against an allowlist of column names, not just sanitized as text. -
Direct $_SERVER superglobal access without sanitization (5 files)
Multiple templates construct URLs from raw $_SERVER['HTTP_HOST'] and $_SERVER['REQUEST_URI'] — these are user-controllable headers:
- views/directory/layout-3.php:72
- views/directory/template-parts/pagination-shortcode.php:46
- views/profile/template-parts/posts-2.php:96
- views/profile/template-parts/comments-2.php:88
Fix: Use sanitize_text_field( wp_unslash( $_SERVER['HTTP_HOST'] ) ) or better yet, use WordPress functions like home_url( add_query_arg( null, null ) ).
- Missing wp_unslash() on $_GET superglobals
- views/directory/template-parts/search-field.php:17
- views/directory/template-parts/row-3.php:135,141,147
- Missing output escaping — XSS vectors
- views/profile/template-parts/posts-2.php:56 — wp_trim_words( get_the_title(), 10 ) echoed without esc_html()
- views/profile/template-parts/posts-2.php:65 — get_permalink() without esc_url()
- views/profile/template-parts/comments-2.php:56 — get_comment_link() without esc_url()
- views/profile/template-parts/comments-2.php:66 — wp_trim_words( $comment->comment_content ) without esc_html()
- views/profile/layout-2.php:90 — Raw SVG icon output without wp_kses()
- views/directory/template-parts/row-3.php:39 — $avatar without wp_kses_post()
-
REST API — Missing nonce verification in frontend JS
assets/js/ud-search-shortcode.js:57-62 — Calls REST API without X-WP-Nonce header. -
REST API permission_callback concerns
Verify that the directory REST endpoints in Api/Directory.php have proper permission_callback functions — public endpoints should explicitly use __return_true, and any write/admin
endpoints must check capabilities.
HIGH Priority Issues
-
Hardcoded REST URL in React component
src/js/user-directory/components/DirectoryList.js:98:
fetch(/wp-json/wpuf/v1/user_directory/user_count?...)
This ignores config.rest_url and will break on non-standard WordPress installations (subdirectory installs, custom REST prefixes). -
ProBadge component defined inside another component body
src/js/user-directory/components/steps/StepAdvanced.js:87 — Defining a React component inside another component's render function causes it to unmount/remount on every render.
ProBadge is also duplicated across 6 files — should be a single shared component. -
Modal accessibility (WCAG 2.1 Level A violation)
src/js/user-directory/components/common/DeleteConfirmModal.js:
- No focus trap
- No role="dialog" or aria-modal="true"
- No aria-labelledby
- "Cancel" and "Delete" button text not wrapped in __() for translation
-
Pagination uses with onClick — not keyboard accessible
src/js/user-directory/components/DirectoryList.js:291-305 — Should be elements with aria-label. -
useEffect dependency array violation
src/js/user-directory/App.js:57-59 — fetchDirectories not in deps and not wrapped in useCallback. Will miss updates to config or perPage. -
Member count fetching — race conditions & performance
src/js/user-directory/components/DirectoryList.js:55-115:
- Multiple setState calls in forEach loop — should be batched
- No AbortController cleanup for in-flight requests on unmount
- State updates on unmounted component warnings
MEDIUM Priority Issues
- in_array() missing strict type comparison
PHPCS config requires true as 3rd parameter. Found ~11 occurrences across:
- views/directory/template-parts/social-icons.php:22-25
- views/profile/layout-2.php:202,233,249,265,283,292
- views/profile/template-parts/file-2.php:101
- Missing ABSPATH check in template files
Several templates lack if ( ! defined( 'ABSPATH' ) ) { exit; }:
- layout-3.php, row-3.php, pagination-shortcode.php, posts-2.php, comments-2.php
-
Functions defined inside template files
wpuf_ud_build_posts_page_url(), wpuf_ud_build_comments_page_url(), wpuf_ud_build_files_page_url() are defined in template files with function_exists() guards. Move these to
Helpers.php. -
Inline <script> blocks in templates
views/profile/layout-2.php has 3 inline <script> blocks. Should use wp_add_inline_script() for CSP compliance. -
posts_per_page => -1 for user attachments
views/profile/template-parts/file-2.php:28-29 — Loads ALL attachments then filters in PHP. Performance risk for users with many uploads. -
Duplicate pagination markup (DRY violation)
Nearly identical pagination HTML in 3 template files. Should be a shared partial. -
Tooltip ID uses Math.random() on every render
src/js/user-directory/components/common/Tooltip.js:5 — Breaks aria-describedby association. Use useId() or useRef. -
navigator.clipboard.writeText — unhandled Promise rejection
src/js/user-directory/components/DirectoryList.js:32 — No .catch(), shows "Copied!" optimistically. -
No form validation before submission
src/js/user-directory/components/DirectoryWizard.js:445 — Can submit a directory with empty title. -
setTimeout without cleanup in DirectoryWizard
Lines 411-417, 424-429, 438-441 — Will cause state updates on unmounted component. -
mailto: link bug
views/directory/template-parts/row-3.php:69:
-
SVG linearGradient ID collision
views/directory/template-parts/social-icons.php — Instagram SVG gradient ID paint0_linear_5793_2712 will collide when multiple user rows render on the same page. -
Tailwind config mixes ESM import with CommonJS module.exports
modules/user-directory/tailwind.config.js:5-8 — May fail in strict CommonJS environments.
| * | ||
| * @package WPUF | ||
| * @subpackage Free/User_Directory | ||
| * @since 4.3.0 |
There was a problem hiding this comment.
do not hardcode version number here, use WPUF_SINCE
| * | ||
| * @package WPUF | ||
| * @subpackage Modules/User_Directory | ||
| * @since 4.3.0 |
There was a problem hiding this comment.
do not hardcode version number here or other places, use WPUF_SINCE
| // Add pagination if there are multiple pages | ||
| if ( $pagination['total_pages'] > 1 ) { | ||
| // Build base URL and query args for pagination | ||
| $current_url = ( is_ssl() ? 'https://' : 'http://' ) . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']; |
There was a problem hiding this comment.
Direct $_SERVER superglobal access without sanitization. also in:
- views/directory/layout-3.php:72
- views/directory/template-parts/pagination-shortcode.php:46
- views/profile/template-parts/posts-2.php:96
- views/profile/template-parts/comments-2.php:88
Add an external file-tabs script and register/enqueue it for profile files; remove inline tab JS. Improve security and robustness across templates: add ABSPATH checks, use esc_url/esc_html/wp_kses/wp_kses_post where appropriate, use wp_unslash for $_GET input, strict in_array checks, and replace raw $_SERVER URL construction with home_url( add_query_arg( null, null ) ). Refactor Helpers: change free layout color palette to emerald, update avatar markup to use Tailwind classes, and add utility helpers (pagination URL builders and wpuf_ud_get_image_size_dimensions). Limit attachment queries to avoid memory issues and tighten file grouping checks. Replace duplicated pagination URL helpers in posts/comments templates with shared helpers. Frontend JS improvements: include X-WP-Nonce header in ud-search-shortcode fetch, add new wpuf-ud-file-tabs.js, and harden React code (useCallback for fetchDirectories, AbortController for member-count fetches, improved batching/loading state for directory member counts, promise-handling fallback for clipboard API). Small doc/version placeholder updates (WPUF_SINCE). These changes aim to improve security, performance, and maintainability.
|
fixed @sapayth vai |
sapayth
left a comment
There was a problem hiding this comment.
@since4.3.0 hardcoded across all new files. useWPUF_SINCEplaceholder.flush_rewrite_rules()is an expensive function call. try to minimize it as much as possible.- Post_Type.php — show_in_rest => true exposes directory config via WP default REST endpoint
/wp-json/wp/v2/wpuf_user_listingbecomes available. Since this module has its own REST controller, set show_in_rest => false to avoid leaking directory settings to authenticated read-capability users. - Helpers.php — $layout parameter silently ignored in wpuf_ud_free_get_layout_colors(). The parameter is accepted but never used. Either use it or remove it — the silent ignore will confuse Pro code that passes different layouts expecting different colors.
- modules.php — wpuf_free_deactivate_module() always returns true. Returns true even when the module was never in the active list, masking errors.
- Free_Loader.php — Inline style attributes on new section headings. Move to the module CSS file.
| } | ||
|
|
||
| // Check URL structure | ||
| $current_uri = trim( $_SERVER['REQUEST_URI'], '/' ); |
There was a problem hiding this comment.
use wp_unslash() and sanitization
| // Flush rewrite rules for pretty URLs | ||
| flush_rewrite_rules(); |
There was a problem hiding this comment.
@arifulhoque7 bhai. flush_rewrite_rules() is an expensive function call. Minimize it as much as possible
| ]; | ||
|
|
||
| foreach ( $profile_params as $param ) { | ||
| if ( ! empty( $_GET[$param] ) ) { |
There was a problem hiding this comment.
use wp_unslash() and sanitization
Apply a set of cleanup and safety fixes for the User Directory free module: - Update @SInCE tags to use WPUF_SINCE across multiple files. - Add admin CSS for module section titles, pro badge and preview container (replace inline styles with classes). - Harden input handling by sanitizing usage of $_GET and $_SERVER in DirectoryStyles. - Remove an unnecessary flush_rewrite_rules() call from REST directory creation and disable REST exposure for the internal wpuf_user_listing post type. - Improve wpuf_free_deactivate_module: validate module existence, return WP_Error for invalid/not-active cases and return null on success; use wpuf_free_get_modules() for checks. - Simplify helpers: free layout color API now always returns the emerald palette and related wrapper functions updated accordingly. - Misc: update docblocks and minor view/template metadata to reflect the changes. These changes improve security, clarity, and correctness of the free User Directory implementation and replace inline styling with maintainable CSS classes.
Multiple fixes and UI refinements for the User Directory module: - Ensure per_page is at least 1 to avoid zero-limit pagination. - Use profile_tabs (instead of default_tabs) in Shortcode and update default profile_tabs order to use 'file' key to match settings. - Fix Tailwind/PostCSS config paths and include Admin_Menu.php in Tailwind content scan. - DirectoryWizard: track WP sidebar folded state reactively (MutationObserver), switch to functional setState updates to avoid stale values, and improve fetch error handling. - MultiSelect: import and use cloneElement instead of React.cloneElement for icon cloning. - SingleSelect: use semantic <li> for option items. - StepAdvanced: correct avatar size to 256 and remove pointerEvents style from ProBadge overlay. - StepTabs: small markup and layout adjustments (heading level change, container maxWidth, minor cleanups). - avatarSizeHelper: correct mapping for layout-6 to 256. These changes address UX bugs, ensure consistent config keys, and improve robustness of state updates and error handling.



PR Title. PRO PR
Close 1379
Feature: Add User Directory Module to WP User Frontend Free
Description
This PR introduces a complete User Directory feature to the free version of WP User Frontend, synced with the Pro version's functionality and design patterns.
Summary of Changes
🎯 Core Feature Implementation
New Module: User Directory (
modules/user-directory/)/directory/username)📁 Files Added (77 files, +22,194 lines)
PHP Classes:
modules/user-directory/User_Directory.phpmodules/user-directory/Admin_Menu.phpmodules/user-directory/Shortcode.phpmodules/user-directory/Post_Type.phpmodules/user-directory/PrettyUrls.phpmodules/user-directory/DirectoryStyles.phpincludes/functions/modules.phpTemplates:
views/directory/layout-3.phpviews/directory/template-parts/pagination-shortcode.phpviews/directory/template-parts/search-field.phpviews/directory/template-parts/sort-field.phpviews/directory/template-parts/social-icons.phpviews/profile/template-parts/about-2.phpviews/profile/template-parts/posts-2.phpviews/profile/template-parts/comments-2.phpviews/profile/template-parts/file-2.phpReact Admin UI (
src/js/user-directory/):index.jsApp.jscomponents/DirectoryList.jscomponents/DirectoryWizard.jscomponents/steps/StepBasics.jscomponents/steps/StepLayout.jscomponents/steps/StepProfile.jscomponents/steps/StepTabs.jscomponents/steps/StepAdvanced.jscomponents/common/Header.jscomponents/common/LayoutCard.jscomponents/common/MultiSelect.jscomponents/common/Toast.jscomponents/common/DeleteConfirmModal.jsBuild Configuration:
webpack.user-directory.config.jstailwind.user-directory.config.jspostcss.user-directory.config.jsAssets:
assets/css/wpuf-user-directory-free.css- Admin styles (7,868 lines)assets/css/wpuf-user-directory-frontend.css- Frontend styles (550 lines)assets/js/wpuf-user-directory-free.js- Compiled admin bundleassets/js/wpuf-user-directory-frontend.js- Frontend scriptsassets/js/ud-search-shortcode.js- AJAX search functionalityassets/images/user-directory/- Layout preview images and iconsKey Features
1. Directory Listing
2. User Profile
3. Setup Wizard
4. Profile URL Handling
/directory/usernameor/directory/123Commits
f531687fc1a5ef37547c53098442e2db4f0a05e6Testing Checklist
Screenshots
Add screenshots of:
Related Issues
Breaking Changes
None. This is a new feature addition.
Dependencies
wpuf-prefix)Summary by CodeRabbit
New Features
Bug Fixes
Chores