Skip to content

Conversation

@iftakharul-islam
Copy link
Member

@iftakharul-islam iftakharul-islam commented Dec 4, 2025

This pull request introduces several improvements focused on WordPress file handling, date/time consistency, and code standards compliance. The most significant changes are the migration to the WordPress Filesystem API for file operations, consistent use of gmdate for UTC-based date/time values, and enhanced code standards documentation and compliance. These updates improve security, reliability, and maintainability across the codebase.

File Handling and Security

  • Migrated direct file operations (such as file_put_contents, file_get_contents, filesize, etc.) to use the WordPress Filesystem API (WP_Filesystem) for better compatibility and security in core/File_System/File_System.php and SVG sanitization logic in bootstrap/loaders.php. [1] [2]

Date and Time Consistency

  • Replaced all uses of date() with gmdate() for storing and displaying dates/times in UTC across upgrade scripts and utility functions (core/Upgrades/Upgrade_2_0.php, core/Upgrades/Upgrade_2_2_1.php, libs/functions.php, core/WP/Menu.php). [1] [2] [3] [4] [5] [6] [7] [8]

Code Standards and Documentation

  • Added a new quick reference guide for PHPCS usage in phpcs-xml/QUICK-REFERENCE.md and included the phpcs-xml directory in .distignore to support code standards enforcement and developer onboarding. [1] [2]

Admin Menu and Global Variable Handling

  • Added explicit phpcs:ignore comments when overriding global variables for WordPress admin menus to clarify intentional design decisions and maintain standards compliance in core/WP/Menu.php and core/Pro/Menu.php. Improved role fetching logic to use wp_roles() when available. [1] [2] [3] [4]

Minor Security and Escaping Improvements

  • Improved escaping and error handling in notification and promotion code to ensure safer output and better debugging. [1] [2] [3]

Summary by CodeRabbit

  • New Features

    • CLI commands for bulk data seeding and safe data purging with progress reporting
    • Three new admin submenu sections for project management (Progress, Reports, Modules)
  • Documentation

    • Added PHPCS quick reference, README and modular rule sets for code quality checks
    • Excluded PHPCS config folder from distribution packaging
  • Improvements

    • Unified UTC-based date/time handling across the app
    • Switched to WordPress filesystem and HTTP APIs for better compatibility
    • Strengthened input validation, ordering controls, and prepared queries for security
  • Bug Fixes

    • Removed stray debug output and improved error message escaping

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

Introduces whitelists of allowed columns for SQL ORDER BY clauses in helper classes to prevent invalid or unsafe column names. Also ensures order directions are validated and all parameters are sanitized before building SQL queries, improving security and robustness across Activity, Comment, Discussion_Board, File, Milestone, Project, Task, Task_List, and User helpers.
Introduces separate XML rulesets for accessibility, general, performance, plugin repository, and security checks under phpcs-xml/. Updates phpcs.xml.dist to reference the new modular rulesets and improves organization and documentation of coding standards for WordPress plugins.
Replaced direct filesystem and random functions with WP_Filesystem and wp_rand for better security and compatibility. Updated all date() usages to gmdate() to avoid timezone issues. Added phpcs:ignore comments for intentional global overrides in admin menu code. Improved output escaping and fixed minor issues flagged by WordPress coding standards. Enabled additional PHPCS rules for performance and general checks.
Introduces comprehensive WP-CLI commands for seeding and cleaning Project Manager data, including users, projects, task lists, tasks, and milestones. Improves robustness in Task and User helpers by handling empty data and preventing SQL errors. Updates Pusher action to correctly detect admin status. Adds '/phpcs-xml' to .distignore.
@iftakharul-islam iftakharul-islam self-assigned this Dec 4, 2025
@coderabbitai
Copy link

coderabbitai bot commented Dec 4, 2025

Walkthrough

Migrates filesystem I/O to WP_Filesystem, converts local date() calls to gmdate() across modules, hardens SQL (prepared statements and ORDER BY whitelists with esc_sql), adds input sanitization, replaces cURL with WP HTTP API, adds CLI seeding/cleanup commands, introduces PHPCS rulesets and docs, and small UI/error-handling tweaks.

Changes

Cohort / File(s) Summary
Filesystem abstraction
/.distignore, bootstrap/loaders.php, core/File_System/File_System.php, src/File/Controllers/File_Controller.php, src/Task/Helper/Task.php
Replace native PHP file ops with WP_Filesystem (init, exists/is_readable/is_writable, get/put_contents, size) and adjust CSV/export streaming to in-memory before output.
UTC date migration
core/Cli/Commands.php, core/Upgrades/Upgrade_2_0.php, core/Upgrades/Upgrade_2_2_1.php, core/WP/Menu.php, libs/functions.php, src/.../Activity.php, src/Milestone/.../Milestone_Controller.php, src/My_Task/.../MyTask_Controller.php, src/Project/.../Project_Transformer.php, src/Pusher/Libs/action.php, src/Task/.../Task_Controller.php, src/Task/.../Task.php, src/Task_List/.../Task_List_Controller.php, src/User/.../User_Transformer.php
Replace date() calls with gmdate() to use UTC/GMT for timestamps and comparisons.
ORDER BY whitelisting & escaping
src/Activity/.../Activity.php, src/Comment/.../Comment.php, src/Discussion_Board/.../Discussion_Board.php, src/File/.../File.php, src/Milestone/.../Milestone.php, src/Project/.../Project.php, src/Task/.../Task.php, src/Task_List/.../Task_List.php, src/User/.../User.php
Introduce whitelists for sortable columns, normalize/validate directions, skip invalid entries, and apply esc_sql() when composing ORDER BY clauses.
Prepared statements & query hardening
src/My_Task/.../MyTask_Controller.php, src/Task_List/.../Task_List_Controller.php, src/Task_List/.../Task_List.php, src/Task/.../Task.php
Convert raw SQL interpolations to $wpdb->prepare() with placeholders; use spread operator for argument arrays where applicable and bind parameters consistently.
Input validation & sanitization
src/Discussion_Board/.../Discussion_Board_Controller.php, src/Project/.../Project_Controller.php, src/Task_List/.../Task_List_Controller.php, src/Discussion_Board/..., various controllers
Add intval(), sanitize_text_field(), wp_kses_post(), FILTER_VALIDATE_BOOLEAN, array/type guards and sanitize castings for controller inputs.
HTTP API migration (cURL → WP HTTP)
src/Imports/Helpers/Trello_helper.php, src/Tools/Library/PM_Asana.php, src/Tools/Helpers/ImportAsana.php, src/Tools/Helpers/ImportTrello.php
Replace cURL with wp_remote_get() / wp_remote_request(), build WP-style args/headers, handle WP_Error and HTTP status codes, remove debug echoes.
CLI commands addition
core/Cli/Commands.php
Add seed_all() and clean_data() WP-CLI commands and switch random helpers to wp_rand(); include progress reporting and summaries.
Exception handling & escaping
core/Notifications/Background_Emailer.php, core/Notifications/Notification.php
Catch global \Exception and escape current_filter() in error messages; escape callback/filter values in error strings.
Menu & promotions
core/Pro/Menu.php, core/Promotions/Offers.php
Add capability-gated submenu items (Progress, Reports, Modules), localize and escape promotional button text, add rel="noopener noreferrer", and relax a nonce/key guard condition.
PHPCS rulesets & docs
phpcs.xml.dist, phpcs-xml/*, phpcs-xml/README.md, phpcs-xml/QUICK-REFERENCE.md
Add modular Plugin Check rulesets (security, performance, general, accessibility, plugin-repo), reorganize top-level phpcs.xml.dist, and add documentation/quick reference.
Miscellaneous safety & formatting
src/Pusher/.../Pusher_Controller.php, src/User/.../User.php, src/Tools/Helpers/ImportActivecollab.php, other helpers
JSON-format Pusher auth output, guard empty IN lists, adjust fake-email prefixes, remove debug echoes, and small whitespace/comment tweaks.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Areas requiring extra attention:

  • src/Task_List/Controllers/Task_List_Controller.php — many prepared statements; verify placeholder order and counts.
  • src/Task/Helper/Task.php — CSV export refactor, WP_Filesystem integration, ORDER BY whitelist, and numerous date-related changes.
  • phpcs.xml.dist and phpcs-xml/* — confirm CI/tooling expectations and exclusions.
  • WP_Filesystem init and error handling across bootstrap/loaders.php and file-system paths.
  • WP HTTP API conversions — auth header semantics and error handling in PM_Asana/Trello helpers.

Possibly related PRs

Suggested labels

Needs Dev Review, Needs Testing

Suggested reviewers

  • MdAsifHossainNadim

Poem

🐇
I hopped through code at break of dawn,
swapped files for WP paths and set dates to GMT on,
I fenced ORDER BY with a careful whitelist,
turned curls to wp_remote, and tidied the tryst —
a small rabbit's patch, snug and done.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 19.35% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Title check ❓ Inconclusive The title 'Fix/security fixures' is vague and contains a typo ('fixures' instead of 'fixtures'). It does not clearly convey the primary changes (UTC date consistency, WordPress Filesystem API migration, PHPCS standards). Revise the title to be more specific and accurate, such as 'Migrate to WP_Filesystem API and standardize UTC date handling' or 'Add WordPress Filesystem API support and UTC date/time consistency'.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/security-fixures

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0f3dea1 and 32a5a43.

📒 Files selected for processing (1)
  • core/Promotions/Offers.php (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • core/Promotions/Offers.php

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

❤️ Share

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

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 10

Caution

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

⚠️ Outside diff range comments (8)
core/Promotions/Offers.php (1)

174-191: Tighten initial POST validation in dismiss_offer()

The guard:

if ( empty( $_POST['nonce'] ) && ! isset( $_POST['pm_offer_key'] ) ) {
    return;
}

only returns when both the nonce is empty and the key is missing. If one is present and the other is not, you proceed and later read $_POST['pm_offer_key'], which can trigger an undefined index notice and lead to updating the option with an empty value.

Recommend short‑circuiting when either parameter is missing:

-        if ( empty( $_POST['nonce'] ) && ! isset( $_POST['pm_offer_key'] ) ) {
+        if ( empty( $_POST['nonce'] ) || ! isset( $_POST['pm_offer_key'] ) ) {
             return;
         }
src/Project/Controllers/Project_Controller.php (1)

394-424: Sanitizing $favourite changed behavior for boolean inputs

After sanitize_text_field( $request->get_param( 'favourite' ) ), $favourite is always a string. That makes the $favourite === true check unreachable and breaks clients that previously sent a JSON boolean true instead of the string 'true' (they will now always hit the “remove from favorite” path).

To support both string and boolean inputs while still sanitizing, consider using your existing pm_is_true() helper and treating $favourite as a boolean:

-        $project_id = intval( $request->get_param( 'id' ) );
-        $favourite  = sanitize_text_field( $request->get_param( 'favourite' ) ) ;
+        $project_id   = intval( $request->get_param( 'id' ) );
+        $favourite_in = $request->get_param( 'favourite' );
+        $favourite    = pm_is_true( $favourite_in );

@@
-        if ( $favourite == 'true' || $favourite === true ) {
+        if ( $favourite ) {
@@
-        if ( $favourite == 'true' ) {
+        if ( $favourite ) {

This preserves backward compatibility for existing boolean callers while keeping the control flow simple.

src/Task/Controllers/Task_Controller.php (2)

769-780: Inconsistent date function usage within the same filter logic.

The overdue case now uses gmdate() (line 769), but today and week cases (lines 772-776) still use date(). This creates inconsistency within the same filter logic.

Additionally, mixing gmdate() with current_time('mysql') (which returns local WordPress time) may lead to incorrect comparisons. Consider either:

  1. Using gmdate() consistently with a UTC time source, or
  2. Using date() consistently with local time
 if( $due_date == 'overdue' ) {
     $today = gmdate( 'Y-m-d', strtotime( current_time('mysql') ) );
     $q->where( 'due_date', '<', $today );
 } else if ( $due_date == 'today' ) {
-    $today = date('Y-m-d', strtotime( current_time('mysql') ) );
+    $today = gmdate( 'Y-m-d', strtotime( current_time('mysql') ) );
     $q->where( 'due_date', $today );
 } else if ( $due_date == 'week' ) {
-    $today = date('Y-m-d', strtotime( current_time('mysql') ) );
-    $last = date('Y-m-d', strtotime( current_time('mysql') . '-1 week' ) );
+    $today = gmdate( 'Y-m-d', strtotime( current_time('mysql') ) );
+    $last = gmdate( 'Y-m-d', strtotime( current_time('mysql') . '-1 week' ) );

     $q->where( 'due_date', '>=', $last );
     $q->where( 'due_date', '<=', $today );
 }

809-821: Same inconsistency exists in the whereHas callback.

Apply the same fix here as noted above for consistency.

 if( $due_date == 'overdue' ) {
     $today = gmdate( 'Y-m-d', strtotime( current_time('mysql') ) );
     $q->where( 'due_date', '<', $today );
 } else if ( $due_date == 'today' ) {
-    $today = date('Y-m-d', strtotime( current_time('mysql') ) );
+    $today = gmdate( 'Y-m-d', strtotime( current_time('mysql') ) );
     $q->where( 'due_date', $today );
 } else if ( $due_date == 'week' ) {
-    $today = date('Y-m-d', strtotime( current_time('mysql') ) );
-    $last = date('Y-m-d', strtotime( current_time('mysql') . '-1 week' ) );
+    $today = gmdate( 'Y-m-d', strtotime( current_time('mysql') ) );
+    $last = gmdate( 'Y-m-d', strtotime( current_time('mysql') . '-1 week' ) );

     $q->where( 'due_date', '>=', $last );
     $q->where( 'due_date', '<=', $today );
 }
src/Comment/Helper/Comment.php (1)

345-346: Incorrect table reference in ORDER BY clause.

The $tb_pj variable references pm_boards, but this is the Comment helper. The ORDER BY clause will reference columns from the wrong table. This should use $this->tb_comment or pm_comments instead.

 private function orderby() {
     global $wpdb;

-    $tb_pj    = $wpdb->prefix . 'pm_boards';
+    $tb_comment = $this->tb_comment;
     $odr_prms = isset( $this->query_params['orderby'] ) ? $this->query_params['orderby'] : false;

And update line 392:

     foreach ( $orders as $key => $value ) {
-        $order[] =  $tb_pj . '.' . esc_sql($key) . ' ' . esc_sql($value);
+        $order[] =  $tb_comment . '.' . esc_sql($key) . ' ' . esc_sql($value);
     }
src/File/Helper/File.php (1)

280-281: Incorrect table reference in ORDER BY clause.

The $tb_pj variable references pm_boards, but this is the File helper. The ORDER BY clause will reference columns from the wrong table. This should use $this->tb_file or pm_files instead.

 private function orderby() {
     global $wpdb;

-    $tb_pj    = $wpdb->prefix . 'pm_boards';
+    $tb_file = $this->tb_file;
     $odr_prms = isset( $this->query_params['orderby'] ) ? $this->query_params['orderby'] : false;

And update line 326:

     foreach ( $orders as $key => $value ) {
-        $order[] =  $tb_pj . '.' . esc_sql($key) . ' ' . esc_sql($value);
+        $order[] =  $tb_file . '.' . esc_sql($key) . ' ' . esc_sql($value);
     }
core/Cli/Commands.php (1)

771-774: Incorrect use of $wpdb->prepare() for table names.

The TRUNCATE TABLE %s pattern is fundamentally broken - $wpdb->prepare() wraps %s in quotes, producing invalid SQL like TRUNCATE TABLE 'wp_pm_activities'. Table identifiers cannot be parameterized this way.

Apply this diff to fix the issue:

 foreach ($tables as $table) {
-    /* translators: %s: Table Name. */
-    $wpdb->query( $wpdb->prepare( "TRUNCATE TABLE %s", $wpdb->prefix . $table ) );
+    $table_name = $wpdb->prefix . $table;
+    // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name is from hardcoded whitelist
+    $wpdb->query( "TRUNCATE TABLE `" . esc_sql( $table_name ) . "`" );
 }
src/Tools/Library/PM_Asana.php (1)

541-560: File upload implementation incompatible with WordPress HTTP API.

The addAttachmentToTask method still constructs CURLFile objects and passes them to askAsana, but askAsana now uses wp_remote_request which does not support CURLFile. This will cause file uploads to fail.

The WordPress HTTP API requires a different approach for file uploads. Consider using WP_Http::request() with a proper multipart body or document that file uploads require additional handling.

 public function addAttachmentToTask($taskId, array $data = array())
 {
-    $mimeType = array_key_exists('mimeType', $data) ? $data['mimeType'] : null;
-    $finalFilename = array_key_exists('finalFilename', $data) ? $data["finalFilename"] : null;
-
-    if (class_exists('CURLFile', false)) {
-        $data['file'] = new CURLFile($data['file'], $data['mimeType'], $data['finalFilename']);
-    } else {
-        $data['file'] = "@{$data['file']}";
-
-        if (!is_null($finalFilename)) {
-            $data['file'] .= ';filename=' . $finalFilename;
-        }
-        if (!is_null($mimeType)) {
-            $data['file'] .= ';type=' . $mimeType;
-        }
-    }
-
-    return $this->askAsana($this->tasksUrl . '/' . $taskId . '/attachments', $data, ASANA_METHOD_POST);
+    // File uploads are not currently supported with WordPress HTTP API
+    // This requires custom multipart handling
+    throw new \Exception('File attachments are not currently supported');
 }
🧹 Nitpick comments (21)
src/Pusher/Src/Controllers/Pusher_Controller.php (1)

36-48: JSON handling for Pusher auth looks correct; consider consistent error response (optional).

Using $auth_response = $pusher->socket_auth( $channel_name, $socket_id ); and then sending it with an explicit Content-Type: application/json header is aligned with Auth::socket_auth() returning a JSON-encoded string, and the PHPCS ignore is justified in this context. This is a clear improvement over the previous direct echo without a content type.

If you want to tighten things further (not required for this PR), you could:

  • Return a JSON error body and Content-Type: application/json in the 403 branch as well, for consistent response semantics from this endpoint.
  • Longer term, consider using wp_send_json() / WP_REST_Response instead of echo/exit to better align with REST patterns.
core/Promotions/Offers.php (1)

48-51: Make fallback button label translatable

The fallback 'Get Now' string is user‑visible but not currently translatable.

You could keep remote action_title as‑is while making the local fallback i18n‑ready:

-            $offer->btn_txt   = ! empty( $promo_notice['action_title'] ) ? $promo_notice['action_title'] : 'Get Now';
+            $offer->btn_txt   = ! empty( $promo_notice['action_title'] )
+                ? $promo_notice['action_title']
+                : __( 'Get Now', 'wedevs-project-manager' );
src/Tools/Helpers/ImportAsana.php (2)

478-478: Add spacing around function arguments for readability.

Line 478 is missing a space after the comma in the string replacement call.

- $hostname = str_replace('http', '',get_site_url());
+ $hostname = str_replace('http', '', get_site_url());

319-325: Prefer explicit isset() checks or null-safe operators for object properties.

Directly accessing object properties like $credentials->email without validation can raise notices if properties don't exist. Consider using isset or the null-safe operator (?.) for clarity.

- if($credentials->email){
+ if(isset($credentials->email) && $credentials->email){
    $email = sanitize_email( $credentials->email );
    error_log($email);
    $user_id = $this->getOrCreateUserId($credentials->name, $email);
  } else {
-   $user_id = $this->getOrCreateUserId($credentials->name,$this->makeFakeEmail($credentials->name));
+   $user_id = $this->getOrCreateUserId($credentials->name, $this->makeFakeEmail($credentials->name));
  }
phpcs-xml/plugin-check-performance.xml (1)

20-32: Misleading comments for rule purposes.

The inline comments don't accurately describe the sniffs:

  • Line 20: "Proper transient usage" but WordPress.WP.CronInterval checks cron scheduling intervals, not transients.
  • Line 29: "Discourage slow string concatenation in loops" but Squiz.PHP.DisallowMultipleAssignments checks for multiple variable assignments in a single statement (e.g., $a = $b = 1), not string concatenation.
-	<!-- Proper transient usage -->
+	<!-- Check for excessively short cron intervals -->
 	<rule ref="WordPress.WP.CronInterval">
 		<type>warning</type>
 	</rule>

 	<!-- =================================== -->
 	<!-- PHP PERFORMANCE -->
 	<!-- =================================== -->

-	<!-- Discourage slow string concatenation in loops -->
+	<!-- Discourage multiple variable assignments in a single statement -->
 	<rule ref="Squiz.PHP.DisallowMultipleAssignments">
 		<type>warning</type>
 	</rule>
phpcs-xml/README.md (2)

7-14: Consider specifying a language for the code block.

The directory structure code block could use a language identifier like text or plaintext for consistency with markdown linting standards.

-```
+```text
 phpcs-xml/
 ├── plugin-check-security.xml       # Security-focused rules
 ...

116-119: Convert bare URL to markdown link for consistency.

The Plugin Check plugin reference uses a bare URL, while other references (lines 123-127) use proper markdown link syntax.

 These rulesets are based on the official WordPress Plugin Check plugin:
-https://wordpress.org/plugins/plugin-check/
+[Plugin Check](https://wordpress.org/plugins/plugin-check/)
phpcs.xml.dist (1)

34-41: Potential duplicate rule inclusions.

The legacy rules section enables WordPress.DB, WordPress.Security, and WordPress.CodeAnalysis, which overlap with rules already included via plugin-check-security.xml and plugin-check-general.xml. This may result in duplicate warnings for the same issues.

Consider either:

  1. Removing the legacy rules section since the plugin-check rulesets cover these
  2. Adding exclusions in the plugin-check XML files to avoid overlap

If the plugin-check rulesets fully cover these, you could simplify:

     <!-- =================================== -->
     <!-- LEGACY RULES (Kept for compatibility) -->
     <!-- =================================== -->
     
     <!-- WordPress Core Standards (optional, can be enabled) -->
     <!-- <rule ref="WordPress-Core">
         <exclude name="Generic.WhiteSpace.DisallowSpaceIndent"/>
     </rule> -->

-    <!-- WordPress Database Rules -->
-    <rule ref="WordPress.DB"/>
-    
-    <!-- WordPress Security Rules -->
-    <rule ref="WordPress.Security"/>
-    
-    <!-- WordPress Code Analysis Rules -->
-    <rule ref="WordPress.CodeAnalysis"/>
+    <!-- Note: WordPress.DB, WordPress.Security, and WordPress.CodeAnalysis 
+         are now included via plugin-check-security.xml and plugin-check-general.xml -->
core/Upgrades/Upgrade_2_0.php (1)

1403-1426: Repeated $now pattern: consider simplifying and/or using full UTC datetime

In gantt_upgrate, add_time_tracker, and insert_client_role you now use:

$now = gmdate( 'Y-m-d', strtotime( current_time( 'mysql' ) ) );

and then persist $now into created_at / updated_at.

  • This yields a UTC date (no time), which matches the pre‑existing date('Y-m-d', ...) behavior except for the timezone shift.
  • The expression is a bit convoluted (WP local time string → strtotime → UTC date) and the timestamp columns still only get a date component.

Not a blocker, but you might consider, in a follow‑up:

  • Either storing a full UTC datetime (e.g. gmdate( 'Y-m-d H:i:s' ) or current_time( 'mysql', true )) for created_at / updated_at, or
  • If you truly only care about the calendar date, simplifying to a more direct “today in UTC” expression and using it consistently across all three sites.

If you decide to adjust this, it’d be good to double‑check any code that compares these timestamps or orders by them so behavior stays consistent.

Also applies to: 1588-1613, 1914-1928

src/Tools/Helpers/ImportTrello.php (1)

429-441: Use URL parsing instead of string replaces for hostname

str_replace( 'http', '', get_site_url() ) can mangle the host (e.g., https://example.com becomes sexample.com). Since you’re already touching this area, it’s safer to derive the host via URL parsing:

-        $hostname = str_replace('http', '',get_site_url());
-        $hostname = str_replace('://', '', $hostname);
+        $hostname = parse_url( get_site_url(), PHP_URL_HOST );
+        if ( empty( $hostname ) ) {
+            $hostname = 'localhost';
+        }

This keeps fake emails well‑formed regardless of scheme.

core/WP/Menu.php (1)

67-101: Remove unused $today in my_task_count()

$today is computed with gmdate() but never used, and PHPMD is flagging it. You can safely drop this line to reduce noise:

-        $today = gmdate( 'Y-m-d', strtotime( current_time( 'mysql' ) ) );
src/Pusher/Libs/action.php (1)

39-52: Safer detection of is_admin in PM_pusher_before_assignees()

The new isset( $_POST['is_admin'] ) && ! empty( intval( pm_clean( $_POST['is_admin'] ) ) ) check avoids undefined index notices while still sanitizing and normalizing the flag to a boolean. You might later consider extracting this pattern into a small helper and reusing it where other functions still read $_POST['is_admin'] directly.

core/File_System/File_System.php (1)

68-73: Redundant require_once statement.

The file.php is already included at line 5 of this file. This require_once is unnecessary.

 // Initialize WP_Filesystem
 global $wp_filesystem;
 if ( empty( $wp_filesystem ) ) {
-    require_once ABSPATH . 'wp-admin/includes/file.php';
     WP_Filesystem();
 }
src/My_Task/Controllers/MyTask_Controller.php (1)

165-172: Pre-existing SQL interpolation concern.

While not introduced in this PR, the direct interpolation of $boards_id, $project_ids, and $user_id into the query string (lines 169, 172, 284-287, 404-407) doesn't use prepared statements. These values come from trusted sources (database queries and get_current_user_id()), but parameterizing them would be more defensive.

Consider addressing this in a follow-up to maintain consistency with the prepared statement improvements made elsewhere in this file.

Also applies to: 284-287

core/Cli/Commands.php (3)

603-612: Direct table name interpolation in SQL queries.

While the table names are constructed from trusted sources ($wpdb->prefix + hardcoded strings), using direct string interpolation in SQL is inconsistent with the security-focused nature of this PR. The $full_table_name variable is safely constructed, but consider using esc_sql() for consistency with the PR's security hardening goals.

Additionally, line 603 uses a raw query for COUNT(*) without $wpdb->prepare(), though this is acceptable since no user input is involved.


88-89: Unused parameters flagged by static analysis.

The $args parameter and $wpdb variable are unused. While $args is required by WP-CLI's callback signature, the $wpdb declaration on line 89 appears to be a leftover from removed code.

-    public function seed_all($args, $assoc_args) {
-        global $wpdb;
+    public function seed_all($args, $assoc_args) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundBeforeLastUsed

182-209: Potential collision in generated usernames.

The seed_users method generates usernames using wp_rand(1000, 9999), which provides only 9,000 unique values. When creating many users (e.g., 50+), collisions become likely, silently skipping user creation.

Consider using a larger range or a unique suffix:

 for ($i = 0; $i < $count; $i++) {
-    $username = 'testuser_' . wp_rand(1000, 9999);
+    $username = 'testuser_' . wp_rand(100000, 999999) . '_' . $i;
     $email = $username . '@testproject.local';
src/Task/Helper/Task.php (1)

93-134: WP_Filesystem initialized but not used.

The code initializes WP_Filesystem (lines 94-99) but then uses native PHP stream functions (fopen, fputcsv, stream_get_contents) for the actual CSV output. The filesystem abstraction serves no purpose here.

Either remove the unused WP_Filesystem initialization or refactor to use $wp_filesystem->put_contents() if the intent is to write to an actual file.

 public static function get_taskscsv( WP_REST_Request $request ) {
-    $self = self::getInstance();
     $tasks = self::get_results( $request->get_params() );

-    // Initialize WP_Filesystem
-    global $wp_filesystem;
-    if ( empty( $wp_filesystem ) ) {
-        require_once ABSPATH . 'wp-admin/includes/file.php';
-        WP_Filesystem();
-    }
-
     header('Content-Type: text/csv; charset=utf-8');
     header('Content-Disposition: attachment; filename=data.csv');
src/Task_List/Controllers/Task_List_Controller.php (2)

95-96: Unused variable $is_archive.

The $is_archive variable is assigned but never used in the index method. Static analysis correctly flagged this.

-        $is_archive  = filter_var($request->get_param('is_archive'), FILTER_VALIDATE_BOOLEAN);

If the variable is intended for future use, consider adding a // phpcs:ignore comment with explanation.


248-248: Unused variable $with.

The $with variable on line 248 is sanitized but never used - the raw parameter is passed to get_list on line 253 instead.

 public function show( WP_REST_Request $request ) {
     $project_id   = intval( $request->get_param( 'project_id' ) );
     $task_list_id = intval( $request->get_param( 'task_list_id' ) );
-    $with         = sanitize_text_field($request->get_param('with'));
     
     return $this->get_list( [
         'project_id'   => $project_id,
         'task_list_id' => $task_list_id,
-        'with'         => $request->get_param( 'with' )
+        'with'         => sanitize_text_field($request->get_param( 'with' ))
     ] );
 }
src/Activity/Helper/Activity.php (1)

31-31: Consider removing unused property.

The $per_page property is declared but never assigned or read in the file. If it's not intended for future use, consider removing it to keep the code clean.

Apply this diff if the property is not needed:

-	private $per_page;
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ebb1e72 and e6c68d9.

⛔ Files ignored due to path filters (2)
  • reports/security copy.csv is excluded by !**/*.csv
  • reports/security.csv is excluded by !**/*.csv
📒 Files selected for processing (45)
  • .distignore (1 hunks)
  • bootstrap/loaders.php (1 hunks)
  • core/Cli/Commands.php (5 hunks)
  • core/File_System/File_System.php (1 hunks)
  • core/Notifications/Background_Emailer.php (1 hunks)
  • core/Notifications/Notification.php (1 hunks)
  • core/Pro/Menu.php (1 hunks)
  • core/Promotions/Offers.php (1 hunks)
  • core/Upgrades/Upgrade_2_0.php (4 hunks)
  • core/Upgrades/Upgrade_2_2_1.php (1 hunks)
  • core/WP/Menu.php (3 hunks)
  • libs/functions.php (3 hunks)
  • phpcs-xml/QUICK-REFERENCE.md (1 hunks)
  • phpcs-xml/README.md (1 hunks)
  • phpcs-xml/plugin-check-accessibility.xml (1 hunks)
  • phpcs-xml/plugin-check-general.xml (1 hunks)
  • phpcs-xml/plugin-check-performance.xml (1 hunks)
  • phpcs-xml/plugin-check-plugin-repo.xml (1 hunks)
  • phpcs-xml/plugin-check-security.xml (1 hunks)
  • phpcs.xml.dist (1 hunks)
  • src/Activity/Helper/Activity.php (3 hunks)
  • src/Comment/Helper/Comment.php (2 hunks)
  • src/Discussion_Board/Controllers/Discussion_Board_Controller.php (3 hunks)
  • src/Discussion_Board/Helper/Discussion_Board.php (2 hunks)
  • src/File/Controllers/File_Controller.php (2 hunks)
  • src/File/Helper/File.php (2 hunks)
  • src/Imports/Helpers/Trello_helper.php (1 hunks)
  • src/Milestone/Controllers/Milestone_Controller.php (3 hunks)
  • src/Milestone/Helper/Milestone.php (2 hunks)
  • src/My_Task/Controllers/MyTask_Controller.php (6 hunks)
  • src/Project/Controllers/Project_Controller.php (2 hunks)
  • src/Project/Helper/Project.php (2 hunks)
  • src/Project/Transformers/Project_Transformer.php (3 hunks)
  • src/Pusher/Libs/action.php (2 hunks)
  • src/Pusher/Src/Controllers/Pusher_Controller.php (1 hunks)
  • src/Task/Controllers/Task_Controller.php (2 hunks)
  • src/Task/Helper/Task.php (10 hunks)
  • src/Task_List/Controllers/Task_List_Controller.php (8 hunks)
  • src/Task_List/Helper/Task_List.php (14 hunks)
  • src/Tools/Helpers/ImportActivecollab.php (1 hunks)
  • src/Tools/Helpers/ImportAsana.php (1 hunks)
  • src/Tools/Helpers/ImportTrello.php (1 hunks)
  • src/Tools/Library/PM_Asana.php (2 hunks)
  • src/User/Helper/User.php (3 hunks)
  • src/User/Transformers/User_Transformer.php (7 hunks)
🧰 Additional context used
🧬 Code graph analysis (12)
src/Discussion_Board/Controllers/Discussion_Board_Controller.php (1)
src/Discussion_Board/Models/Discussion_Board.php (2)
  • files (39-41)
  • Discussion_Board (14-64)
src/Pusher/Libs/action.php (1)
libs/functions.php (1)
  • pm_clean (969-975)
src/Comment/Helper/Comment.php (2)
src/User/Helper/User.php (1)
  • orderby (171-224)
src/Discussion_Board/Helper/Discussion_Board.php (1)
  • orderby (636-690)
src/Pusher/Src/Controllers/Pusher_Controller.php (1)
src/Pusher/Core/Auth/Auth.php (1)
  • socket_auth (36-61)
src/User/Helper/User.php (8)
src/Task/Helper/Task.php (1)
  • orderby (1837-1899)
src/Activity/Helper/Activity.php (1)
  • orderby (534-603)
src/Task_List/Helper/Task_List.php (1)
  • orderby (921-978)
src/Comment/Helper/Comment.php (1)
  • orderby (342-398)
src/Discussion_Board/Helper/Discussion_Board.php (1)
  • orderby (636-690)
src/File/Helper/File.php (1)
  • orderby (277-332)
src/Milestone/Helper/Milestone.php (1)
  • orderby (532-588)
src/Project/Helper/Project.php (1)
  • orderby (1301-1360)
src/My_Task/Controllers/MyTask_Controller.php (2)
src/Task/Helper/Task.php (2)
  • where_users (1279-1307)
  • get_results (146-166)
src/Activity/Helper/Activity.php (1)
  • get_results (48-67)
src/Tools/Helpers/ImportActivecollab.php (1)
libs/validation-rules.php (1)
  • email (11-13)
src/Task_List/Helper/Task_List.php (7)
src/Task/Helper/Task.php (1)
  • orderby (1837-1899)
src/Activity/Helper/Activity.php (1)
  • orderby (534-603)
src/Comment/Helper/Comment.php (1)
  • orderby (342-398)
src/Discussion_Board/Helper/Discussion_Board.php (1)
  • orderby (636-690)
src/File/Helper/File.php (1)
  • orderby (277-332)
src/Milestone/Helper/Milestone.php (1)
  • orderby (532-588)
src/Project/Helper/Project.php (1)
  • orderby (1301-1360)
src/Task_List/Controllers/Task_List_Controller.php (5)
src/Task_List/Observers/Task_List_Observer.php (2)
  • status (58-72)
  • title (32-38)
libs/functions.php (1)
  • pm_update_meta (235-247)
src/Common/Traits/Transformer_Manager.php (1)
  • get_response (11-27)
src/Task_List/Transformers/List_Task_Transformer.php (1)
  • assignees (140-179)
src/Task_List/Models/Task_List.php (1)
  • assignees (65-69)
core/Pro/Menu.php (1)
libs/functions.php (1)
  • pm_has_manage_capability (413-427)
src/Discussion_Board/Helper/Discussion_Board.php (8)
src/Task/Helper/Task.php (1)
  • orderby (1837-1899)
src/User/Helper/User.php (1)
  • orderby (171-224)
src/Activity/Helper/Activity.php (1)
  • orderby (534-603)
src/Task_List/Helper/Task_List.php (1)
  • orderby (921-978)
src/Comment/Helper/Comment.php (1)
  • orderby (342-398)
src/File/Helper/File.php (1)
  • orderby (277-332)
src/Milestone/Helper/Milestone.php (1)
  • orderby (532-588)
src/Project/Helper/Project.php (1)
  • orderby (1301-1360)
core/WP/Menu.php (1)
libs/functions.php (3)
  • pm_user_can_access (435-448)
  • pm_manager_cap_slug (1318-1320)
  • pm_has_admin_capability (393-406)
🪛 LanguageTool
phpcs-xml/QUICK-REFERENCE.md

[style] ~71-~71: This adverb was used twice in the sentence. Consider removing one of them or replacing them with a synonym.
Context: ...ow summary only - --severity=7 - Show only errors with severity 7 or higher ## Ex...

(ADVERB_REPETITION_PREMIUM)

🪛 markdownlint-cli2 (0.18.1)
phpcs-xml/README.md

7-7: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


117-117: Bare URL used

(MD034, no-bare-urls)

🪛 PHPMD (2.15.0)
src/Activity/Helper/Activity.php

31-31: Avoid unused private fields such as '$per_page'. (undefined)

(UnusedPrivateField)

src/Task_List/Controllers/Task_List_Controller.php

95-95: Avoid unused local variables such as '$is_archive'. (undefined)

(UnusedLocalVariable)


248-248: Avoid unused local variables such as '$with'. (undefined)

(UnusedLocalVariable)

src/File/Controllers/File_Controller.php

112-112: Avoid unused local variables such as '$file_name'. (undefined)

(UnusedLocalVariable)

core/Cli/Commands.php

88-88: Avoid unused parameters such as '$args'. (undefined)

(UnusedFormalParameter)


89-89: Avoid unused local variables such as '$wpdb'. (undefined)

(UnusedLocalVariable)


517-517: Avoid unused parameters such as '$args'. (undefined)

(UnusedFormalParameter)

core/WP/Menu.php

68-68: Avoid unused local variables such as '$today'. (undefined)

(UnusedLocalVariable)

src/Task/Helper/Task.php

91-91: Avoid unused local variables such as '$self'. (undefined)

(UnusedLocalVariable)


117-117: Avoid unused local variables such as '$key'. (undefined)

(UnusedLocalVariable)

🔇 Additional comments (52)
core/Notifications/Background_Emailer.php (1)

48-48: Good security improvement.

Escaping the callback filter value before including it in the error message is a solid defense-in-depth practice that prevents potential output issues if error messages are displayed in admin interfaces or logs.

core/Notifications/Notification.php (1)

110-112: Excellent fixes: namespace correction + escaping improvement.

Two solid improvements here:

  • Line 110: Using \Exception correctly references the global Exception class rather than looking for a non-existent namespaced Exception.
  • Line 112: Escaping current_filter() output aligns with the security hardening goals of this PR.
src/Tools/Helpers/ImportActivecollab.php (1)

513-515: Email prefix update looks good, but depends on hostname fix.

The change from activecol_ to activecollab_ improves clarity. The conditional domain logic (append .com if no dot present) is appropriate for generating fake emails. However, these lines will still produce invalid emails until the hostname extraction bug (lines 510-511) is resolved.

src/Tools/Helpers/ImportAsana.php (2)

474-487: Debug output successfully removed from email generation function.

The removal of the debug echo statement from makeFakeEmail is clean and preserves the hostname sanitization logic. The function correctly generates and sanitizes fake email addresses.

However, note that this file still contains multiple error_log() calls throughout (lines 135, 160, 222, 255, 263, 310–311, 321, 346, 381–382, 402, 417, 491–492). If the PR aims for complete debug cleanup, consider whether these should also be removed or evaluated for production use.


393-401: No changes required — the code is already secure.

The wpdb->insert() function automatically calls $wpdb->prepare() internally and handles data escaping without additional parameters. This is the standard WordPress pattern and is used consistently throughout the codebase. The data sources ($comment->text, $pm_task_id, $pm_project_id, $user_id) are properly escaped by the database layer.

src/User/Transformers/User_Transformer.php (2)

298-299: Good: Proper UTC date handling in graph date range initialization and loop.

The use of gmdate() for converting GET parameters (lines 298-299), calculating relative dates (line 302), and incrementing dates in the loop (line 314) correctly normalizes dates to UTC format. This aligns with the PR's goal of UTC-based date handling.

Also applies to: 302-302, 314-314


146-146: The gmdate() usage here is correct and requires no changes.

The database stores dates in MySQL TIMESTAMP fields, which automatically handle UTC conversion. When these values are retrieved, they are already in UTC format as strings, and gmdate('Y-m-d', strtotime($item['due_date'])) correctly formats them. This pattern is used consistently throughout the codebase (Task_List_Controller.php, Task_Controller.php, Project_Transformer.php, etc.) and is the appropriate way to handle UTC-stored dates in WordPress.

Likely an incorrect or invalid review comment.

.distignore (1)

51-51: LGTM!

Correctly excludes the PHPCS configuration directory from distribution. Development tooling should not be shipped with the production plugin.

phpcs-xml/QUICK-REFERENCE.md (1)

1-124: Helpful developer documentation.

The quick reference guide is well-structured with practical examples for running security audits, generating reports, and using git hooks. The static analysis hint about adverb repetition on line 71 is a false positive—the phrases "Show summary only" and "Show only errors" appear in separate list items with distinct meanings.

phpcs-xml/plugin-check-accessibility.xml (1)

1-30: LGTM!

Accessibility ruleset appropriately uses warning severity for HTML-in-PHP detection sniffs, which can have false positives. The ruleset is disabled by default in phpcs.xml.dist, allowing teams to opt-in when ready.

phpcs-xml/plugin-check-general.xml (1)

1-61: LGTM!

The ruleset is well-organized with appropriate severity levels. GlobalVariablesOverride correctly uses error severity since overwriting WordPress globals can cause hard-to-debug issues.

Note: Generic.Functions.OpeningFunctionBraceKernighanRitchie enforces K&R brace style—ensure this aligns with the project's existing code style to avoid conflicts.

phpcs.xml.dist (1)

63-64: Good addition for debugging.

Adding <arg value="s"/> to show sniff codes in the output is helpful for debugging and selectively excluding specific rules.

phpcs-xml/plugin-check-security.xml (2)

1-204: Comprehensive security ruleset structure and organization—well-designed.

The ruleset is logically organized into clear categories with inline documentation. Rules are appropriately configured with severity levels (6–7 for critical issues, warnings for non-blocking checks). The inclusion of WordPress.DateTime.RestrictedFunctions (line 154) aligns with the PR's gmdate() migration goal. Overall architecture supports the PR's security hardening objectives.


1-204: The ruleset already covers HTTP API enforcement via WordPress.WP.AlternativeFunctions.

The WordPress.WP.AlternativeFunctions rule (lines 213–222) already flags cURL usage and recommends WordPress HTTP API functions (wp_remote_get, wp_remote_post, wp_remote_request, WP_Http). No additional rule is needed for basic HTTP API migration enforcement.

core/Upgrades/Upgrade_2_0.php (1)

1205-1226: UTC normalization of invoice payment date — just confirm downstream expectations

Switching the stored payment date to gmdate( 'Y-m-d', strtotime( $payment_date ) ) normalizes it to a UTC date, which matches the PR’s goal. For sites previously running in a non‑UTC timezone, this can shift the persisted calendar date by ±1 day compared to the old date()‑based behavior. Please confirm anything that reads the invoice_payment meta (filters, reports, UI) is either timezone‑agnostic or explicitly expects this field to be UTC‑normalized, not “blog local” date.

core/WP/Menu.php (1)

21-53: PHPCS ignores around $submenu are appropriately scoped

The new phpcs:ignore annotations are narrowly targeting the intentional $submenu overrides and explain the rationale, which is a good balance between standards and required behavior.

core/Pro/Menu.php (1)

44-55: New Pro submenus are correctly capability‑gated

Adding Calendar unconditionally and Progress/Reports/Modules only when pm_has_manage_capability() is true keeps advanced views restricted while leaving read‑only calendar access. PHPCS ignores are correctly scoped to the intentional $submenu overrides.

src/Project/Controllers/Project_Controller.php (2)

35-56: Sanitizing query params in index() is a good hardening step

Casting per_page/page to int and sanitizing status and project_transform, plus int‑casting category, all reduce risk from malformed or unexpected request data while keeping the rest of the logic unchanged.


164-185: Casting id in show() improves robustness

Using intval( $request->get_param( 'id' ) ) before Project::find() prevents non‑numeric IDs from leaking into the ORM, which is safer for both routing and DB access.

libs/functions.php (3)

105-110: UTC‑based date formatting in pm_date_format()

Switching to gmdate() here makes this helper return UTC‑normalized dates, which is consistent with the rest of the PR’s move to UTC timing and avoids server‑locale surprises.


573-580: UTC timestamps in pm_log()

Using gmdate() for log timestamps standardizes logs to UTC, which is typically preferable for debugging across environments and aligns with other UTC changes in this PR.


124-139: pm_get_wp_roles() correctly uses wp_roles()->get_names() as the recommended approach

The implementation follows WordPress core best practices by calling wp_roles()->get_names() for WordPress 4.3+, which is the officially documented and recommended method for retrieving role labels. The guarded fallback to new WP_Roles() properly handles older WordPress versions. The PHPCS ignore comment is appropriately narrowed in scope to only the fallback initialization, and is justified for backward compatibility.

core/Upgrades/Upgrade_2_2_1.php (1)

24-32: Milestone achieve_date upgrade now normalizes to UTC

Converting $meta_value->date with gmdate( 'Y-m-d H:i:s', strtotime( ... ) ) makes the upgraded achieve_date meta UTC‑based, matching the broader UTC standardization in this release without altering the surrounding upgrade logic.

src/Pusher/Libs/action.php (1)

24-32: UTC normalization for due_date comparison

Using gmdate() for both $original_due and $updated_due keeps the comparison on a consistent UTC day boundary, which aligns with the rest of the date/time changes and avoids local‑timezone discrepancies when deciding whether to emit a due‑date update.

core/File_System/File_System.php (1)

75-87: Good migration to WP_Filesystem API.

The use of $wp_filesystem->put_contents() and $wp_filesystem->size() properly replaces direct PHP file operations. Error handling for write failure is correctly implemented.

src/Comment/Helper/Comment.php (1)

352-392: Whitelist-based column validation and escaping looks good.

The implementation correctly:

  • Defines a whitelist of allowed columns
  • Normalizes direction to lowercase
  • Validates column against whitelist with strict comparison
  • Validates direction against allowed values
  • Applies esc_sql() to both column and direction

This pattern is consistent with other helpers in the codebase.

src/Milestone/Helper/Milestone.php (1)

542-582: Proper ORDER BY whitelist implementation.

The implementation correctly validates columns against a whitelist that includes milestone-specific fields like achieve_date, normalizes direction, and applies escaping. This is consistent with the pattern used across other helpers.

src/Discussion_Board/Controllers/Discussion_Board_Controller.php (3)

108-118: Good input validation for file deletion.

Proper validation that files_to_delete is an array before applying intval to each element, with a safe default to an empty array.


211-226: Proper sanitization of user IDs.

Good pattern: validate array type, convert to integers, and filter out zero values before processing. This prevents invalid or malicious user IDs from being processed.


242-247: Consistent sanitization in detach_users.

The same validation pattern is applied consistently for detaching users.

src/Discussion_Board/Helper/Discussion_Board.php (1)

646-684: Consistent ORDER BY whitelist implementation.

The implementation follows the same secure pattern as other helpers, with proper column whitelisting, direction validation, and SQL escaping.

src/File/Helper/File.php (1)

287-320: Whitelist validation logic is correct.

The column whitelist and validation logic are properly implemented for file-specific columns. Just needs the table reference fix noted above.

src/Project/Helper/Project.php (1)

1311-1354: Good security hardening for ORDER BY clause.

The whitelist-based validation and esc_sql() escaping properly prevent SQL injection via user-controlled orderby parameters. This is consistent with the same pattern applied across other helper files in this PR.

src/Task_List/Helper/Task_List.php (2)

179-180: Spread operator usage for $wpdb->prepare() is correct.

The change from $wpdb->prepare($query, $query_data) to $wpdb->prepare($query, ...$query_data) properly unpacks the array into individual arguments. This pattern is applied consistently across multiple methods in this file.


931-972: Good whitelist-based ORDER BY validation.

The implementation correctly validates columns against an allowed list and normalizes direction values. This aligns with the security hardening pattern applied across other helper classes.

bootstrap/loaders.php (1)

167-186: Proper migration to WP_Filesystem API.

The implementation correctly:

  • Initializes WP_Filesystem with the standard WordPress pattern
  • Replaces all direct PHP file operations with their WP_Filesystem equivalents
  • Uses FS_CHMOD_FILE for appropriate file permissions
  • Preserves error handling flow
src/My_Task/Controllers/MyTask_Controller.php (3)

28-28: Good change to gmdate() for UTC consistency.

Using gmdate() ensures UTC-based date calculations, aligning with the broader date/time normalization effort in this PR.


177-295: Prepared statements properly parameterize date and meta_key values.

The multisite query correctly uses placeholders for $meta_key and the four occurrences of $start date. This prevents SQL injection via these parameters.


490-492: Correct prepared statement for user role query.

The change properly parameterizes $user_id with %d placeholder, ensuring type safety.

src/Task/Helper/Task.php (3)

1849-1893: Good implementation of ORDER BY column whitelisting.

The whitelist approach for allowed columns (lines 1850-1863) combined with direction validation (lines 1882-1885) and esc_sql() escaping (line 1893) provides robust protection against SQL injection in dynamic ORDER BY clauses.


329-344: Good defensive handling for missing users.

Returning a structured placeholder object with sensible defaults (like 'display_name' => 'Unknown User') when a user doesn't exist prevents null pointer exceptions downstream and provides graceful degradation.


161-163: Good null safety for single-query results.

The early return of an empty array when no results exist prevents potential errors when accessing $response['data'][0] on empty results.

src/Tools/Library/PM_Asana.php (1)

1363-1390: Clean migration to WordPress HTTP API.

The request argument assembly and method-specific body handling are well-structured. The use of wp_remote_request with proper timeout, headers, and SSL verification settings follows WordPress best practices.

src/Task_List/Controllers/Task_List_Controller.php (2)

609-699: Well-implemented parameterized query construction.

The query building logic correctly:

  1. Sanitizes list IDs with array_map('intval', ...) (line 609)
  2. Builds placeholders dynamically (line 610)
  3. Accumulates filter values separately from the query string
  4. Uses $wpdb->prepare() with spread operator for final execution (line 699)

This is a solid security improvement.


543-547: Good input validation for orders parameter.

Adding array type validation before processing $orders prevents type errors and provides a clear error response.

src/Project/Transformers/Project_Transformer.php (2)

106-107: Potential timezone inconsistency.

The code uses current_time('mysql') (which returns WordPress local time) but formats it with gmdate() (which interprets timestamps as UTC). This combination doesn't actually convert to UTC - strtotime() on a datetime string assumes local timezone.

If UTC consistency is the goal, consider:

-$today     = gmdate( 'Y-m-d', strtotime( current_time( 'mysql' ) ) );
-$first_day = gmdate( 'Y-m-d', strtotime('-1 month') );
+$today     = current_time( 'Y-m-d', true ); // true = GMT
+$first_day = gmdate( 'Y-m-d', strtotime('-1 month', current_time('timestamp', true)) );

Alternatively, if the local timezone is intentional for user-facing graphs, using date() would be more semantically clear. Please verify the intended behavior.


117-141: Consistent gmdate usage in date iteration.

The date grouping and iteration logic consistently uses gmdate() for formatting, which ensures uniform date representation across the graph data.

src/Milestone/Controllers/Milestone_Controller.php (1)

111-111: LGTM! UTC migration for achieve_date timestamps.

The conversion from date() to gmdate() ensures consistent UTC-based storage of milestone achieve dates across all create and update operations. This aligns with the broader UTC standardization effort in the PR.

Also applies to: 147-147, 198-198

src/User/Helper/User.php (2)

139-142: LGTM! Prevents SQL error from empty IN clause.

The guard correctly prevents execution when the prepared ID list is empty, avoiding SQL syntax errors. This defensive check is consistent with similar improvements across other helpers in this PR.


181-219: LGTM! Essential security hardening for dynamic ORDER BY.

The orderby enhancements introduce critical security improvements:

  • Column name whitelist prevents SQL injection through orderby parameters
  • Direction validation normalizes to 'asc'/'desc' with safe defaults
  • esc_sql() escaping provides defense-in-depth

This implementation is consistent with the pattern applied across other helpers (Activity, Comment, File, Milestone, Project, Task, Task_List, Discussion_Board) in this PR.

src/Activity/Helper/Activity.php (2)

353-354: LGTM! UTC migration for date range calculations.

The conversion from date() to gmdate() ensures consistent UTC-based date calculations for the BETWEEN query conditions. This aligns with the broader UTC standardization effort across the PR.


543-603: LGTM! Essential security hardening for dynamic ORDER BY with dual format support.

The orderby enhancements introduce critical security improvements:

  • Column name whitelist prevents SQL injection through orderby parameters
  • Supports both string ('col|dir,col|dir') and array (['col'=>'dir']) formats
  • Direction validation normalizes to 'asc'/'desc' with safe defaults
  • Validates pair structure (skips malformed pairs)
  • esc_sql() escaping provides defense-in-depth

This implementation is consistent with the security pattern applied across other helpers in this PR, with additional robustness for handling multiple input formats.

<exclude-pattern>*/cherry-framework/*</exclude-pattern>
<exclude-pattern>*/titan-framework/*</exclude-pattern>
<exclude-pattern>*/plugin-update-checker/*</exclude-pattern>
<exclude-pattern>*/composer_directory/*</exclude-pattern>
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# Check if composer_directory exists as a directory
fd -t d "composer_directory" 2>/dev/null || echo "Directory not found"

Repository: weDevsOfficial/wp-project-manager

Length of output: 59


🏁 Script executed:

# Search for references to composer_directory in the entire codebase
rg "composer_directory" --type-list | head -20
rg "composer_directory" 2>/dev/null || echo "No references found"

Repository: weDevsOfficial/wp-project-manager

Length of output: 581


🏁 Script executed:

# Examine the phpcs config file to understand the exclude pattern context
cat -n phpcs-xml/plugin-check-plugin-repo.xml

Repository: weDevsOfficial/wp-project-manager

Length of output: 2825


🏁 Script executed:

# Check if there's a main phpcs config referenced
fd -t f "phpcs.xml*" -o "phpcs-ruleset*" 2>/dev/null | head -20

Repository: weDevsOfficial/wp-project-manager

Length of output: 59


Remove or clarify the composer_directory exclude pattern.

The pattern */composer_directory/* does not correspond to any directory in the codebase and has no other references in the project. This appears to be dead code or a typo. Either remove it or document why it's needed; if the intent was to exclude Composer dependencies, use the standard */vendor/* pattern instead (which aligns with typical Composer setup).

🤖 Prompt for AI Agents
In phpcs-xml/plugin-check-plugin-repo.xml around line 47, the exclude-pattern
entry "*/composer_directory/*" does not match anything in the repo and should be
removed or clarified; either delete this dead/typo pattern or replace it with
the standard Composer exclusion "*/vendor/*" if the intent was to skip Composer
dependencies, and if you keep a nonstandard name add a comment explaining why
that specific path must be excluded.

Comment on lines +54 to +58
<!-- Plugins should be compatible with PHP 5.6 and higher (updated from 5.2) -->
<config name="testVersion" value="5.6-"/>

<!-- Check for PHP compatibility issues -->
<rule ref="PHPCompatibility"/>
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

fd -name "composer.json" -type f

Repository: weDevsOfficial/wp-project-manager

Length of output: 307


🌐 Web query:

WordPress 6.7 minimum PHP version requirement

💡 Result:

WordPress 6.7 minimum PHP requirement: PHP 7.2.24 or newer. [1][2]

Sources:

  • WordPress Hosting Handbook (server requirements table). [1]
  • WordPress Trac / core tickets preparing 6.7 for PHP versions. [2]

🏁 Script executed:

find . -name "composer.json" -type f

Repository: weDevsOfficial/wp-project-manager

Length of output: 92


🏁 Script executed:

cat composer.json 2>/dev/null | head -50

Repository: weDevsOfficial/wp-project-manager

Length of output: 1586


🏁 Script executed:

# Also check if there's any documentation about minimum PHP version for this plugin checker
fd "README" -type f | head -5

Repository: weDevsOfficial/wp-project-manager

Length of output: 247


🏁 Script executed:

cat phpcs-xml/plugin-check-plugin-repo.xml

Repository: weDevsOfficial/wp-project-manager

Length of output: 2405


🏁 Script executed:

# Search for any documentation or comments about PHP version targets
rg "testVersion|PHP.*7\.[0-9]|6\.[0-9]" phpcs-xml/ -A 2 -B 2

Repository: weDevsOfficial/wp-project-manager

Length of output: 443


PHP 5.6 compatibility target is outdated; update to PHP 7.2 minimum.

PHP 5.6 reached end-of-life in December 2018. WordPress 6.7 (current) requires PHP 7.2.24 minimum. Update the testVersion to align with current WordPress requirements.

-	<!-- Plugins should be compatible with PHP 5.6 and higher (updated from 5.2) -->
-	<config name="testVersion" value="5.6-"/>
+	<!-- Plugins should be compatible with PHP 7.2 and higher (WordPress 6.7+ requirement) -->
+	<config name="testVersion" value="7.2-"/>

The PHPCompatibility package is already properly installed in composer.json as a require-dev dependency.

📝 Committable suggestion

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

Suggested change
<!-- Plugins should be compatible with PHP 5.6 and higher (updated from 5.2) -->
<config name="testVersion" value="5.6-"/>
<!-- Check for PHP compatibility issues -->
<rule ref="PHPCompatibility"/>
<!-- Plugins should be compatible with PHP 7.2 and higher (WordPress 6.7+ requirement) -->
<config name="testVersion" value="7.2-"/>
<!-- Check for PHP compatibility issues -->
<rule ref="PHPCompatibility"/>
🤖 Prompt for AI Agents
In phpcs-xml/plugin-check-plugin-repo.xml around lines 54 to 58, the testVersion
is set to "5.6-" which is outdated; change it to target PHP 7.2 minimum (e.g.
"7.2-") so the PHPCompatibility checks match current WordPress requirements,
updating the value of the config name="testVersion" to "7.2-" to enforce PHP
7.2+ compatibility.

Comment on lines +173 to +178
<rule ref="WordPress.WP.AlternativeFunctions">
<type>error</type>
<exclude name="WordPress.WP.AlternativeFunctions.json_encode_json_encode"/>
<exclude name="WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents"/>
<exclude name="WordPress.WP.AlternativeFunctions.file_system_operations_file_put_contents"/>
</rule>
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Remove exclusions for file_get_contents and file_put_contents—contradicts PR migration goals.

Lines 175-177 exclude warnings for file_get_contents and file_put_contents via WordPress.WP.AlternativeFunctions, yet the PR explicitly aims to migrate these direct PHP file operations to the WordPress Filesystem API (WP_Filesystem). Suppressing these warnings undermines the security hardening goals and would allow future code to bypass the intended standard.

Remove the exclusions so PHPCS enforces the migration to WP_Filesystem:

 	<rule ref="WordPress.WP.AlternativeFunctions">
 		<type>error</type>
-		<exclude name="WordPress.WP.AlternativeFunctions.json_encode_json_encode"/>
-		<exclude name="WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents"/>
-		<exclude name="WordPress.WP.AlternativeFunctions.file_system_operations_file_put_contents"/>
+		<exclude name="WordPress.WP.AlternativeFunctions.json_encode_json_encode"/>
📝 Committable suggestion

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

Suggested change
<rule ref="WordPress.WP.AlternativeFunctions">
<type>error</type>
<exclude name="WordPress.WP.AlternativeFunctions.json_encode_json_encode"/>
<exclude name="WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents"/>
<exclude name="WordPress.WP.AlternativeFunctions.file_system_operations_file_put_contents"/>
</rule>
<rule ref="WordPress.WP.AlternativeFunctions">
<type>error</type>
<exclude name="WordPress.WP.AlternativeFunctions.json_encode_json_encode"/>
</rule>
🤖 Prompt for AI Agents
In phpcs-xml/plugin-check-security.xml around lines 173 to 178, the rule block
currently excludes file_get_contents and file_put_contents which prevents PHPCS
from flagging direct PHP file operations; remove the two <exclude> entries named
"WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents" and
"WordPress.WP.AlternativeFunctions.file_system_operations_file_put_contents" so
PHPCS will enforce migrating to WP_Filesystem, and ensure the resulting XML
remains well-formed (keep the json_encode exclusion if intended).

die( esc_html__( 'file not found', 'wedevs-project-manager' ) );
}

$file_name = basename( $path );
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Remove unused variable $file_name.

This variable is assigned but never used. Line 130 uses basename($path) directly instead.

-        $file_name = basename( $path );
📝 Committable suggestion

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

Suggested change
$file_name = basename( $path );
🧰 Tools
🪛 PHPMD (2.15.0)

112-112: Avoid unused local variables such as '$file_name'. (undefined)

(UnusedLocalVariable)

🤖 Prompt for AI Agents
In src/File/Controllers/File_Controller.php around line 112, the variable
$file_name is assigned with basename($path) but never used later (the code calls
basename($path) directly at line 130); remove the unused assignment to eliminate
dead code — delete the line that sets $file_name = basename($path); so the code
uses basename($path) where needed without the unused variable.

Comment on lines +132 to +133
// Replace readfile with WP_Filesystem method
echo $wp_filesystem->get_contents( $path );
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Potential memory issue with large file downloads.

Replacing readfile() with echo $wp_filesystem->get_contents() loads the entire file into memory before outputting. For large files, this could exhaust PHP memory limits.

Consider using WP_Filesystem's direct file reading with output buffering or streaming, or keeping readfile() for the download functionality since it streams content directly.

-            // Replace readfile with WP_Filesystem method
-            echo $wp_filesystem->get_contents( $path );
+            // For downloads, readfile() is more memory-efficient as it streams content
+            // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_readfile
+            readfile( $path );
🤖 Prompt for AI Agents
In src/File/Controllers/File_Controller.php around lines 132-133, replacing
readfile() with echo $wp_filesystem->get_contents($path) can exhaust PHP memory
for large files because it loads the whole file into memory; revert to a
streaming approach instead — either restore readfile($path) (preferred for
direct streaming) or open the file and stream in chunks (fopen +
while/fpassthru/streamed read) while preserving existing download headers and
ensuring output buffering is flushed and disabled before streaming.

Comment on lines 36 to 55
public function make_request($source,$querystring,$calback){
// Get cURL resource
$curl = curl_init();
// Set some options - we are passing in a useragent too here
curl_setopt_array($curl, [
CURLOPT_RETURNTRANSFER => 1,
CURLOPT_URL => $this->remote_addr($source,$querystring)
// Use WordPress HTTP API instead of cURL
$url = $this->remote_addr($source,$querystring);

// Make the request using wp_remote_get
$response = wp_remote_get($url, [
'timeout' => 30,
'sslverify' => true
]);
// Send the request & save response to $resp
$resp = curl_exec($curl);
// Close request to clear up some resources
curl_close($curl);

// Check for errors
if (is_wp_error($response)) {
return $calback(json_encode(['error' => $response->get_error_message()]));
}

// Get the response body
$resp = wp_remote_retrieve_body($response);

return $calback($resp);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🌐 Web query:

WordPress wp_remote_get() best practices HTTP status code error handling

💡 Result:

Practical best practices for wp_remote_get() and HTTP status‑code handling

  • Always check for transport errors first using is_wp_error(). If true, log/handle the WP_Error. [1]
  • Then check the HTTP status code with wp_remote_retrieve_response_code(). Treat only 2xx (usually 200) as success; handle other codes explicitly. [1][2]
    • 200: process body (wp_remote_retrieve_body()) and verify/parse (e.g., json_decode + JSON error checks). [1][2]
    • 3xx: follow redirects (WP HTTP does by default) or handle location header if you expect specific redirect logic. [2]
    • 4xx (client errors): don’t retry immediately; surface useful messages, back off, and validate request parameters/auth. 429 (rate limit): implement exponential backoff and honor Retry‑After header if present. [2][3]
    • 5xx (server errors): retry with exponential backoff a limited number of times; escalate/alert if persistent. [2][3]
  • Respect and read headers: use wp_remote_retrieve_header() for Retry‑After, ETag/Last‑Modified for conditional requests, and rate‑limit headers. Use conditional requests to reduce load. [2]
  • Timeouts and redirection: set sensible args (timeout, redirection) and a clear user‑facing fallback for long/blocking requests. Avoid huge timeouts on front‑end requests. [1][3]
    Example args: ['timeout'=>5, 'redirection'=>5, 'user-agent'=>..., 'headers'=>[...] ]. [1][3]
  • Use caching (transients or object cache) for API responses to avoid repeated network calls; refresh in background (cron or WP‑CLI) and use ETag/Last‑Modified to validate cache. Don’t cache enormous payloads. [3]
  • Retries & backoff: implement capped retries with exponential backoff for intermittent network/server errors (5xx, timeouts). For 429, respect Retry‑After header if provided. [2][3]
  • Security: never disable SSL verification in production; validate and sanitize all returned data before use. Use authentication headers securely (never embed secrets in client‑side code). [2][3]
  • Non‑blocking / background: for user‑facing pages, use non‑blocking requests (blocking=>false) or async background jobs to avoid slowing page loads. [1][3]
  • Limit response size and validate content-type: check Content‑Type and limit processing for very large responses to avoid memory issues. [1][7]
  • Instrumentation: log status codes, latencies, errors, and retry attempts so you can monitor external API reliability and adjust timeouts/retry policies. [2][3]

Minimal example pattern

  • Do is_wp_error() check → read response code → if 200 parse and validate body → else branch by code (429 backoff, 4xx show error, 5xx retry). Cache successful payloads and honor headers like ETag/Retry‑After. [1][2][3]

References

  • wp_remote_get() reference and helpers (wp_remote_retrieve_body/response_code). [1]
  • “Getting data from an external service” — WordPress Common APIs Handbook (status codes, headers, conditional requests). [2]
  • Practical tips: caching, retries, timeouts and handling rate limits. [3]

Sources
[1] WordPress Developer Reference: wp_remote_get().
[2] WordPress Developer Handbook — Getting data from an external service.
[3] Best‑practice guides: caching & retry examples (transients/background refresh, rate‑limit handling).


🌐 Web query:

WordPress wp_remote_retrieve_response_code() documentation external API

💡 Result:

wp_remote_retrieve_response_code() — short summary, usage, and behavior

  • Purpose: returns the HTTP status code from a response array returned by WordPress HTTP functions (wp_remote_get, wp_remote_post, wp_remote_request). [1][2]

  • Signature: wp_remote_retrieve_response_code( array|WP_Error $response ) : int|string. Returns the numeric HTTP status code (e.g. 200, 404) or an empty string if the parameter is invalid or a WP_Error. [1]

  • Typical usage:
    $response = wp_remote_get( 'https://api.example.com/endpoint' );
    if ( is_wp_error( $response ) ) {
    // handle transport error
    } else {
    $code = wp_remote_retrieve_response_code( $response );
    if ( 200 === (int) $code ) {
    $body = wp_remote_retrieve_body( $response );
    // process body
    } else {
    // handle non-200 HTTP status
    }
    } [2][3]

  • Implementation note: it reads $response['response']['code'] and returns '' when $response is a WP_Error or the expected array keys are missing. [1]

Sources:
[1] Function reference: wp_remote_retrieve_response_code() — Developer.WordPress.org.
[2] Making HTTP requests / Getting data from an external service — Developer.WordPress.org.
[3] wp_remote_get() function reference — Developer.WordPress.org.


Migrate to wp_remote_get() and add HTTP status code validation

Good move switching to WordPress's HTTP API for better SSL/proxy handling and cleaner error management via is_wp_error() and wp_remote_retrieve_body(). However, the implementation is incomplete: it handles transport errors but treats all HTTP responses (including 4xx/5xx) as successful. When consuming external APIs like Trello, non-2xx responses should be treated as errors.

Add HTTP status code validation after the error check:

         // Check for errors
         if (is_wp_error($response)) {
             return $calback(json_encode(['error' => $response->get_error_message()]));
         }

+        // Validate HTTP status code
+        $code = wp_remote_retrieve_response_code($response);
+        if ((int)$code < 200 || (int)$code >= 300) {
+            return $calback(json_encode(['error' => 'HTTP ' . $code]));
+        }
+
         // Get the response body
         $resp = wp_remote_retrieve_body($response);

         return $calback($resp);

This prevents silently treating Trello error responses (404, 429, 500, etc.) as valid data.

📝 Committable suggestion

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

Suggested change
public function make_request($source,$querystring,$calback){
// Get cURL resource
$curl = curl_init();
// Set some options - we are passing in a useragent too here
curl_setopt_array($curl, [
CURLOPT_RETURNTRANSFER => 1,
CURLOPT_URL => $this->remote_addr($source,$querystring)
// Use WordPress HTTP API instead of cURL
$url = $this->remote_addr($source,$querystring);
// Make the request using wp_remote_get
$response = wp_remote_get($url, [
'timeout' => 30,
'sslverify' => true
]);
// Send the request & save response to $resp
$resp = curl_exec($curl);
// Close request to clear up some resources
curl_close($curl);
// Check for errors
if (is_wp_error($response)) {
return $calback(json_encode(['error' => $response->get_error_message()]));
}
// Get the response body
$resp = wp_remote_retrieve_body($response);
return $calback($resp);
}
public function make_request($source,$querystring,$calback){
// Use WordPress HTTP API instead of cURL
$url = $this->remote_addr($source,$querystring);
// Make the request using wp_remote_get
$response = wp_remote_get($url, [
'timeout' => 30,
'sslverify' => true
]);
// Check for errors
if (is_wp_error($response)) {
return $calback(json_encode(['error' => $response->get_error_message()]));
}
// Validate HTTP status code
$code = wp_remote_retrieve_response_code($response);
if ((int)$code < 200 || (int)$code >= 300) {
return $calback(json_encode(['error' => 'HTTP ' . $code]));
}
// Get the response body
$resp = wp_remote_retrieve_body($response);
return $calback($resp);
}
🤖 Prompt for AI Agents
In src/Imports/Helpers/Trello_helper.php around lines 36 to 55, after the
is_wp_error() check add HTTP status validation: retrieve the response code
(using wp_remote_retrieve_response_code or $response['response']['code']) and if
it is not a 2xx value, treat it as an error — call the provided $calback with a
JSON-encoded error that includes the HTTP status and the response body (from
wp_remote_retrieve_body) so non-2xx Trello responses (404/429/500, etc.) aren’t
treated as successful.

Comment on lines 510 to +511
$hostname = str_replace('http', '',get_site_url());
$hostname = str_replace('://', '',$hostname);
echo $hostname;
$hostname = str_replace('://', '', $hostname);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Critical bug: Hostname extraction breaks for HTTPS URLs.

The current approach removes 'http' first (line 510), leaving 's://' for HTTPS sites, then removes '://' (line 511), resulting in 's:example.com'. This produces invalid email addresses like activecollab_user@s:example.com because the colon violates email format standards.

Apply this diff to correctly extract the hostname:

 $mailuser = preg_replace('/[^A-Za-z0-9\-]/', '', $mailuser);
-$hostname = str_replace('http', '',get_site_url());
-$hostname = str_replace('://', '', $hostname);
+$hostname = parse_url(get_site_url(), PHP_URL_HOST);
 if (strpos($hostname, ".")) {
🤖 Prompt for AI Agents
In src/Tools/Helpers/ImportActivecollab.php around lines 510-511, the hostname
extraction currently strips 'http' then '://', which breaks HTTPS (leaving
's:example.com'); replace this logic by parsing the site URL to extract the host
component (e.g., use parse_url(get_site_url(), PHP_URL_HOST) or WP's
wp_parse_url) and fall back to get_site_url() or rtrim as needed; ensure the
final hostname contains only the host (no scheme, port, or colon) before being
used in email generation.

Comment on lines +1398 to +1404
echo '<pre>';
echo 'WordPress HTTP API Error: ' . esc_html($response->get_error_message());
echo '</pre>';
echo '<br>Sent info:<br><pre>';
print_r($data);
echo '</pre>';
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Debug output may expose sensitive data.

When $this->debug or $this->advDebug is enabled, print_r($data) outputs the request body which may contain sensitive API keys, tokens, or user data. Consider sanitizing or limiting debug output.

 if ($this->debug || $this->advDebug) {
     echo '<pre>';
     echo 'WordPress HTTP API Error: ' . esc_html($response->get_error_message());
     echo '</pre>';
-    echo '<br>Sent info:<br><pre>';
-    print_r($data);
-    echo '</pre>';
+    if ($this->advDebug) {
+        echo '<br>Sent data type: ' . esc_html(gettype($data));
+    }
 }
📝 Committable suggestion

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

Suggested change
echo '<pre>';
echo 'WordPress HTTP API Error: ' . esc_html($response->get_error_message());
echo '</pre>';
echo '<br>Sent info:<br><pre>';
print_r($data);
echo '</pre>';
}
echo '<pre>';
echo 'WordPress HTTP API Error: ' . esc_html($response->get_error_message());
echo '</pre>';
if ($this->advDebug) {
echo '<br>Sent data type: ' . esc_html(gettype($data));
}
}
🤖 Prompt for AI Agents
In src/Tools/Library/PM_Asana.php around lines 1398-1404, the current debug path
echoes the raw $data which can expose sensitive tokens/keys; replace the raw
print_r with a sanitized representation: when $this->debug or $this->advDebug is
enabled, create a copy of $data and for any keys matching sensitive patterns
(e.g. token|key|secret|password|authorization|access_token) redact or mask their
values, truncate very long values, or alternatively only output keys and value
types/counts, then echo the sanitized output instead of the raw $data.

public function includeMeta ( User $user ) {
return $this->item ('', function () use ( $user ) {
$today = date( 'Y-m-d', strtotime( current_time( 'mysql' ) ) );
$today = gmdate( 'Y-m-d', strtotime( current_time( 'mysql' ) ) );
Copy link

@coderabbitai coderabbitai bot Dec 4, 2025

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Critical: Incorrect timezone handling with current_time() and gmdate().

The pattern gmdate( 'Y-m-d', strtotime( current_time( 'mysql' ) ) ) is problematic because current_time('mysql') returns local WordPress time, not UTC. When passed through strtotime() and gmdate(), this creates timezone confusion and produces incorrect dates for sites in non-UTC timezones, affecting task filtering and graph calculations.

Apply this diff to use proper UTC date handling:

-$today = gmdate( 'Y-m-d', strtotime( current_time( 'mysql' ) ) );
+$today = gmdate( 'Y-m-d' );

For all four occurrences at lines 116, 162, 182, and 301. Alternatively, use:

$today = current_datetime()->setTimezone( new DateTimeZone( 'UTC' ) )->format( 'Y-m-d' );

Also applies to: 162-162, 182-182, 301-301

🤖 Prompt for AI Agents
In src/User/Transformers/User_Transformer.php around lines 116, 162, 182 and
301, replace the incorrect timezone handling that uses gmdate('Y-m-d',
strtotime(current_time('mysql'))) with a proper UTC-aware DateTime approach;
specifically obtain the current WP datetime, set its timezone to UTC, and format
as 'Y-m-d' (e.g. current_datetime()->setTimezone(new
DateTimeZone('UTC'))->format('Y-m-d')), ensuring each of the four occurrences is
updated so dates are computed in UTC rather than converting local WP time via
strtotime/gmdate.

Copy link
Member Author

Choose a reason for hiding this comment

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

this is the current_time function body do you think it will make problem ?
@CodeRabbit

/**

  • Retrieves the current time based on specified type.

    • The 'mysql' type will return the time in the format for MySQL DATETIME field.
    • The 'timestamp' or 'U' types will return the current timestamp or a sum of timestamp
  • and timezone offset, depending on $gmt.

    • Other strings will be interpreted as PHP date formats (e.g. 'Y-m-d').
  • If $gmt is a truthy value then both types will use GMT time, otherwise the

  • output is adjusted with the GMT offset for the site.

  • @SInCE 1.0.0

  • @SInCE 5.3.0 Now returns an integer if $type is 'U'. Previously a string was returned.

  • @param string $type Type of time to retrieve. Accepts 'mysql', 'timestamp', 'U',

  •                 or PHP date format string (e.g. 'Y-m-d').
    
  • @param bool $gmt Optional. Whether to use GMT timezone. Default false.

  • @return int|string Integer if $type is 'timestamp' or 'U', string otherwise.
    */
    function current_time( $type, $gmt = false ) {
    // Don't use non-GMT timestamp, unless you know the difference and really need to.
    if ( 'timestamp' === $type || 'U' === $type ) {
    return $gmt ? time() : time() + (int) ( (float) get_option( 'gmt_offset' ) * HOUR_IN_SECONDS );
    }

    if ( 'mysql' === $type ) {
    $type = 'Y-m-d H:i:s';
    }

    $timezone = $gmt ? new DateTimeZone( 'UTC' ) : wp_timezone();
    $datetime = new DateTime( 'now', $timezone );

    return $datetime->format( $type );
    }

Copy link

Choose a reason for hiding this comment

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

@iftakharul-islam Yes, this will cause a problem. Let me explain why with the function body you've provided:

The Problem

When you call current_time('mysql') without the second parameter (or with $gmt = false):

$timezone = $gmt ? new DateTimeZone('UTC') : wp_timezone(); // Uses site's local timezone
$datetime = new DateTime('now', $timezone);
return $datetime->format('Y-m-d H:i:s'); // Returns LOCAL time like "2025-12-04 15:30:00"

This returns a datetime string in your site's local timezone (e.g., EST, PST, etc.).

Then when you do:

$today = gmdate('Y-m-d', strtotime(current_time('mysql')));

Here's what happens:

  1. current_time('mysql') returns "2025-12-04 15:30:00" in your site's timezone (e.g., EST)
  2. strtotime() parses this string, but it interprets it in PHP's default timezone context (which may differ from WordPress timezone)
  3. gmdate() then formats the result as UTC

This mixing of timezone contexts causes incorrect date calculations, especially:

  • Near midnight boundaries (you might get yesterday's or tomorrow's date)
  • For sites in timezones far from UTC
  • When comparing dates stored in UTC in your database

The Solution

Use one of these approaches to get a proper UTC date:

Option 1 (Simplest):

$today = gmdate('Y-m-d'); // Uses current Unix timestamp, formats in UTC

Option 2 (Using current_time correctly):

$today = current_time('Y-m-d', true); // Second param true = get GMT/UTC time

Option 3 (Most explicit):

$today = current_datetime()->setTimezone(new DateTimeZone('UTC'))->format('Y-m-d');

All these ensure you're working in a consistent UTC context throughout. I recommend Option 1 (gmdate('Y-m-d')) as it's the simplest and most direct.

@Rubaiyat-E-Mohammad Rubaiyat-E-Mohammad added QA in Progress QA Approved This PR is approved by the QA team and removed QA in Progress labels Dec 4, 2025
Corrected the conditional logic in Offers::dismiss_offer to use OR instead of AND, ensuring proper validation of POST parameters. Updated Task_Controller to consistently use gmdate for date comparisons, improving date handling and consistency across different due date filters.
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Caution

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

⚠️ Outside diff range comments (3)
src/Task/Controllers/Task_Controller.php (2)

767-780: Inconsistent date/time handling mixes UTC and local time.

Lines 769, 772, and 775 now use gmdate() for $today, but line 777 still uses date() for $last. This creates a date range where one boundary is in UTC and the other is in local time, causing incorrect filtering when the server timezone differs from UTC.

Additionally, the pattern gmdate('Y-m-d', strtotime(current_time('mysql'))) is problematic because current_time('mysql') without the second parameter returns local time, which is then parsed and reformatted as GMT—this can introduce subtle timezone conversion issues.

Apply this diff to use UTC consistently:

         if ( ! empty(  $due_date ) ) {
             if( $due_date == 'overdue' ) {
-                $today = gmdate( 'Y-m-d', strtotime( current_time('mysql') ) );
+                $today = gmdate('Y-m-d', current_time('timestamp', true));
                 $q->where( 'due_date', '<', $today );
             } else if ( $due_date == 'today' ) {
-                $today = gmdate('Y-m-d', strtotime( current_time('mysql') ) );
+                $today = gmdate('Y-m-d', current_time('timestamp', true));
                 $q->where( 'due_date', $today );
             } else if ( $due_date == 'week' ) {
-                $today = gmdate('Y-m-d', strtotime( current_time('mysql') ) );
-                $last = date('Y-m-d', strtotime( current_time('mysql') . '-1 week' ) );
+                $today = gmdate('Y-m-d', current_time('timestamp', true));
+                $last = gmdate('Y-m-d', strtotime('-1 week', current_time('timestamp', true)));
 
                 $q->where( 'due_date', '>=', $last );
                 $q->where( 'due_date', '<=', $today );
             }
         }

807-821: Inconsistent date/time handling mixes UTC and local time (duplicate code).

This code block has the same UTC/local time inconsistency as lines 767-780: $today uses gmdate() but $last on line 817 uses date(), creating incorrect date ranges for week filtering.

Additionally, this entire filtering logic (lines 797-836) duplicates the logic in lines 755-794. Consider extracting this into a private method to reduce duplication and ensure consistency.

Apply this diff to fix the date handling:

         if ( ! empty(  $due_date ) ) {
             if( $due_date == 'overdue' ) {
-                $today = gmdate( 'Y-m-d', strtotime( current_time('mysql') ) );
+                $today = gmdate('Y-m-d', current_time('timestamp', true));
                 $q->where( 'due_date', '<', $today );
             } else if ( $due_date == 'today' ) {
-                $today = gmdate('Y-m-d', strtotime( current_time('mysql') ) );
+                $today = gmdate('Y-m-d', current_time('timestamp', true));
                 $q->where( 'due_date', $today );
             } else if ( $due_date == 'week' ) {
-                $today = gmdate('Y-m-d', strtotime( current_time('mysql') ) );
-                $last = date('Y-m-d', strtotime( current_time('mysql') . '-1 week' ) );
+                $today = gmdate('Y-m-d', current_time('timestamp', true));
+                $last = gmdate('Y-m-d', strtotime('-1 week', current_time('timestamp', true)));
 
                 $q->where( 'due_date', '>=', $last );
                 $q->where( 'due_date', '<=', $today );
             }
         }

To eliminate duplication, extract the due date filtering logic into a reusable method that both closures can call.

core/Promotions/Offers.php (1)

43-44: Timezone handling inconsistent with PR objectives.

The PR objectives emphasize UTC consistency across date/time handling, but this code uses America/New_York timezone for promotional offer scheduling. This creates a timezone dependency that may cause confusion for international deployments.

Consider migrating to UTC-based comparisons for consistency with the broader codebase changes.

♻️ Duplicate comments (1)
core/Promotions/Offers.php (1)

136-138: Security issue remains unaddressed: missing rel="noopener noreferrer".

The anchor tag still opens in a new tab with target="_blank" but lacks the rel attribute, leaving the site vulnerable to reverse-tabnabbing. This was previously flagged in an earlier review.

Apply the suggested fix from the previous review:

-                        <a class="link" target="_blank" href="<?php echo esc_url( $offer->link ); ?>">
+                        <a class="link" target="_blank" rel="noopener noreferrer" href="<?php echo esc_url( $offer->link ); ?>">
                             <?php echo esc_html( $offer->btn_txt ); ?>
                         </a>
🧹 Nitpick comments (3)
core/Promotions/Offers.php (3)

50-50: Localize the fallback button text.

The fallback 'Get Now' should be wrapped in a translation function for consistency with WordPress i18n practices.

-        $offer->btn_txt   = ! empty( $promo_notice['action_title'] ) ? $promo_notice['action_title'] : 'Get Now';
+        $offer->btn_txt   = ! empty( $promo_notice['action_title'] ) ? $promo_notice['action_title'] : __( 'Get Now', 'wedevs-project-manager' );

52-58: Remove unnecessary translation wrappers.

The sprintf( __( '%s', ... ), $variable ) pattern provides no i18n value since the content comes from external JSON and contains no translatable text. This adds unnecessary overhead.

-            $offer->message[] = sprintf( __( '<strong>%s</strong>', 'wedevs-project-manager' ), $promo_notice['title'] );
+            $offer->message[] = '<strong>' . $promo_notice['title'] . '</strong>';

             if ( ! empty( $promo_notice['description'] ) ) {
-                $offer->message[] = sprintf( __( '%s', 'wedevs-project-manager' ), $promo_notice['description'] );
+                $offer->message[] = $promo_notice['description'];
             }

-            $offer->message[] = sprintf( __( '%s', 'wedevs-project-manager' ), $promo_notice['content'] );
+            $offer->message[] = $promo_notice['content'];

82-82: Consider sanitizing direct superglobal access.

While the value is only used for comparison, sanitizing input aligns with the PR's focus on security improvements and WordPress coding standards.

-        if ( ! isset( $_GET['page'] ) || 'pm_projects' !== $_GET['page'] ) {
+        if ( ! isset( $_GET['page'] ) || 'pm_projects' !== sanitize_text_field( wp_unslash( $_GET['page'] ) ) ) {
             return;
         }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e6c68d9 and 0f3dea1.

📒 Files selected for processing (2)
  • core/Promotions/Offers.php (2 hunks)
  • src/Task/Controllers/Task_Controller.php (2 hunks)
🔇 Additional comments (2)
core/Promotions/Offers.php (2)

137-137: LGTM: Proper escaping for button text.

The esc_html() function correctly escapes the button text for safe HTML output, protecting against XSS if the external JSON source is compromised.


174-174: LGTM: Improved guard condition logic.

Changing from && to || strengthens the validation by ensuring both required parameters are present before proceeding. This correctly rejects requests missing either the nonce or the offer key, aligning with secure input validation patterns.

Wrapped the default button text 'Get Now' in a translation function for internationalization support. Added rel="noopener noreferrer" to offer links for improved security when opening in a new tab.
@iftakharul-islam
Copy link
Member Author

@copilot Please the coderabbit suggestions here and give a overall implementations suggestion according to our projects requirements

Copy link
Contributor

Copilot AI commented Dec 4, 2025

@iftakharul-islam I've opened a new pull request, #569, to work on those changes. Once the pull request is ready, I'll request review from you.

@iftakharul-islam iftakharul-islam merged commit 32a5a43 into develop Dec 4, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

QA Approved This PR is approved by the QA team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants