Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughRegisters a Metrics REST controller and dashboard; changes gateway send() success returns to structured arrays; logs SMS send results into a new Changes
Sequence Diagram(s)sequenceDiagram
participant Client as Client App
participant Tools as Tools.php
participant Gateway as Gateway
participant Dispatcher as Dispatcher
participant DB as Database
Client->>Tools: request send SMS (to, message)
Tools->>Gateway: send(to, message)
Gateway-->>Tools: return WP_Error OR { success, reference_id }
Tools->>Dispatcher: do_action('texty_after_send_sms', result, to, message, gateway)
Dispatcher->>DB: INSERT INTO texty_sms_stat(receiver,gateway,status,reference_id,created_at,updated_at)
DB-->>Dispatcher: insert result
Tools-->>Client: return send result
sequenceDiagram
participant Browser as Browser
participant Dashboard as Dashboard.js
participant WPAPI as WordPress REST API
participant Metrics as Metrics.php
participant DB as Database
Browser->>Dashboard: mount
Dashboard->>WPAPI: GET /texty/v1/metrics
WPAPI->>Metrics: handle request -> get_metrics
Metrics->>DB: queries for monthly, last_month, 30-day delivery, 12-month aggregates
DB-->>Metrics: query results
Metrics-->>WPAPI: metrics payload
WPAPI-->>Dashboard: metrics JSON
Dashboard->>Browser: render StatCards + VolumeChart + QuickSend
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 1 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (1 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 10
♻️ Duplicate comments (2)
includes/Api/Tools.php (1)
64-68: Duplicate logging logic — same asSend.php.This block is identical to the one in
includes/Api/Send.php(lines 69–73). See the refactoring suggestion in theSend.phpreview to centralize this intoDispatcher::maybe_log_sms().🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@includes/Api/Tools.php` around lines 64 - 68, The SMS-success logging block in Tools.php duplicates the logic in Send.php; replace the inline check and call to Dispatcher::log_sms(...) with a single call to the centralized helper Dispatcher::maybe_log_sms($to, $status) (created per the Send.php review), and ensure maybe_log_sms implements the status validation (is_wp_error, is_array, isset success) and calls Dispatcher::log_sms($to, $reference_id) internally so this file no longer repeats the conditional logic.includes/Dispatcher.php (1)
87-104:⚠️ Potential issue | 🔴 CriticalColumn name
timestampdoes not exist in the table schema — see related comment onInstall.php.The insert on line 97 references a
timestampcolumn, butInstall.php::create_tables()definescreated_atandupdated_atinstead. Additionally,updated_atisNOT NULLwith no default and is never provided. This will cause every$wpdb->insert()call to fail. See the detailed fix options in theInstall.phpreview comment.Also consider specifying
$formatin$wpdb->insert()for explicitness and type safety:♻️ Example with format parameter
- $result = $wpdb->insert( $table_name, $data ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery + $format = [ '%s', '%s', '%s', '%s', '%s' ]; + $result = $wpdb->insert( $table_name, $data, $format ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@includes/Dispatcher.php` around lines 87 - 104, In log_sms() the insert uses a non-existent 'timestamp' column and omits the NOT NULL 'updated_at' column defined in Install.php::create_tables(), causing inserts to fail; replace the 'timestamp' key with 'created_at' and also provide an 'updated_at' value (e.g., same current_time('mysql') value) in the $data array, and call $wpdb->insert( $table_name, $data, $format ) with an appropriate $format array to match column types for explicitness and type safety.
🧹 Nitpick comments (8)
includes/Gateways/Clickatell.php (1)
132-135:reference_idis alwaysnull— Clickatell 202 responses include message IDs.Other gateways extract the provider's message ID from the response body (e.g., Twilio's
sid, Vonage'smessage-id, Plivo'smessage_uuid). For Clickatell, the 202 response body contains message identifiers that could be parsed and returned here for consistent traceability.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@includes/Gateways/Clickatell.php` around lines 132 - 135, The current return hardcodes 'reference_id' => null in the Clickatell gateway; update the send/response-handling logic in the Clickatell class to parse the provider response body (typically JSON) when a 202 is received and extract the message identifier (e.g., look for a top-level "messages" array and use messages[0].id or messages[0].messageId, or fallback to any provider-specific id field) and return it as 'reference_id' instead of null; also include a sensible fallback (e.g., check response headers like X-Message-Id or return null only if no id found) so the returned array mirrors other gateways' use of the provider message ID.includes/Gateways/Fake.php (1)
7-11: Nit: Class docblock still says "Twilio Class" — likely a copy-paste leftover./** - * Twilio Class + * Fake Gateway Class * - * `@see` https://www.twilio.com/docs/sms/api/message */🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@includes/Gateways/Fake.php` around lines 7 - 11, The class docblock incorrectly labels this as "Twilio Class"—update the docblock above the Fake gateway class (class name Fake) to accurately describe this gateway (e.g., "Fake Gateway" or "Fake SMS gateway") and adjust or remove the Twilio-specific `@see` URL so the comment matches the implementation and intent.includes/Api/Send.php (1)
64-75: Duplicated logging logic — extract to a shared helper.The logging block (lines 69–73) is identical to the one in
includes/Api/Tools.php(lines 64–68). Consider extracting this into a reusable method (e.g., onDispatcheror a base class) to avoid copy-paste drift.♻️ Example: centralize logging in Dispatcher
Add a helper to
Dispatcher:public static function maybe_log_sms( $to, $status ) { if ( ! is_wp_error( $status ) && is_array( $status ) && ! empty( $status['success'] ) ) { $reference_id = isset( $status['reference_id'] ) ? $status['reference_id'] : null; self::log_sms( $to, $reference_id ); } }Then both call sites simplify to:
- // Log the SMS if it was sent successfully - if ( ! is_wp_error( $status ) && is_array( $status ) && isset( $status['success'] ) && $status['success'] ) { - $reference_id = isset( $status['reference_id'] ) ? $status['reference_id'] : null; - Dispatcher::log_sms( $to, $reference_id ); - } + Dispatcher::maybe_log_sms( $to, $status );🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@includes/Api/Send.php` around lines 64 - 75, Extract the duplicated SMS-success logging into a shared helper on Dispatcher and replace both inline blocks with calls to it: add a public static function maybe_log_sms($to, $status) on class Dispatcher that checks ! is_wp_error($status), is_array($status) and ! empty($status['success']), pulls $reference_id if set, then calls self::log_sms($to, $reference_id); update the call sites in Send.php and Tools.php to call Dispatcher::maybe_log_sms($to, $status) instead of duplicating the conditional logging logic.src/components/HelpResources.js (1)
5-30: Move theresourcesarray outside the component to avoid re-allocating it on every render.The array is pure static data and doesn't depend on props or state. Defining it at module scope avoids the repeated allocation and makes the component body easier to scan.
♻️ Proposed refactor
+const resources = [ + { + id: 'docs', + title: __('Documentation', 'texty'), + // ... + }, + // ... +]; + function HelpResources() { - const resources = [ - { - id: 'docs', - // ... - }, - // ... - ];Note:
__()calls are safe to evaluate at module level in a WordPress JS context — they read from the loaded translation data synchronously.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/HelpResources.js` around lines 5 - 30, The resources array is recreated on each render; move the const resources = [...] definition out of the component function to module scope so it is allocated once (keep the same identifier resources and the existing item keys like id/title/description/url/buttonText/icon), leaving the component to reference resources without changes; keep the __() calls in place (they are safe at module level) and ensure no props/state are referenced inside resources after the move.src/components/VolumeChart.js (1)
35-41: Hardcoded Y-axis domain[0, 10000]with static ticks won't adapt to actual data.For most WordPress plugin installs with low SMS volume, all bars will render near zero height. For high-volume installs (>10,000/month), bars will be clipped. Use Recharts'
'auto'upper bound so the axis scales to the data.♻️ Proposed fix
- <YAxis - domain={[0, 10000]} - ticks={[0, 2500, 5000, 7500, 10000]} - tick={{ fill: '#9ca3af', fontSize: 13 }} - axisLine={false} - tickLine={false} - /> + <YAxis + domain={[0, 'auto']} + tick={{ fill: '#9ca3af', fontSize: 13 }} + axisLine={false} + tickLine={false} + allowDecimals={false} + />🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/VolumeChart.js` around lines 35 - 41, The YAxis in VolumeChart.js is hardcoded to domain={[0, 10000]} and static ticks, so it won’t scale to real data; update the YAxis (component name: YAxis in VolumeChart) to use an adaptive upper bound (e.g., domain={[0, 'auto']}) and remove or compute static ticks so Recharts will scale the axis to the data (or compute ticks from the dataset inside the VolumeChart component before rendering) ensuring bars don’t clip or sit near zero.src/app.css (1)
371-377:button-primaryin the quick-send form uses amber (#f59e0b) while the rest of the stylesheet uses green (#16a34a) as the brand color.All other interactive elements (stat card icons, header active links, progress fill, help resource card hover) use the green palette. The amber here appears inconsistent unless it was intentional to visually distinguish the send action.
♻️ Align with brand color if unintentional
.texty-quick-send-form .button-primary { - background-color: `#f59e0b`; - color: white; - border: 1px solid `#f59e0b`; + background-color: `#16a34a`; + color: white; + border: 1px solid `#16a34a`; width: 100%; padding: 10px 16px; } .texty-quick-send-form .button-primary:hover:not(:disabled) { - background-color: `#d97706`; - border-color: `#d97706`; + background-color: `#15803d`; + border-color: `#15803d`; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/app.css` around lines 371 - 377, The .texty-quick-send-form .button-primary rule uses amber (`#f59e0b`) which is inconsistent with the project's brand green; update the CSS selector .texty-quick-send-form .button-primary to use the brand green (`#16a34a`) for background-color and border (keep color: white and existing width/padding), so the quick-send button matches other interactive elements like stat card icons/Header active links/progress fill/help card hover.includes/Api.php (1)
27-27:get_volume_chart()issues 12 sequentialSELECT COUNT(*)queries — consolidate into one.The private helper in
includes/Api/Metrics.php(lines ~107–121) executes a separate$wpdb->get_var()per month inside aforeach. Replace with a single aggregation query and merge the results against the expected month list to fill zeros:♻️ Proposed refactor
private function get_volume_chart( $table_name ) { global $wpdb; // Generate expected months first (using corrected strtotime approach) $months = []; $chart_data = []; for ( $i = 11; $i >= 0; $i-- ) { $months[] = gmdate( 'Y-m', strtotime( "-{$i} months", current_time( 'timestamp' ) ) ); } // Single aggregation query $placeholders = implode( ',', array_fill( 0, count( $months ), '%s' ) ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared $rows = $wpdb->get_results( $wpdb->prepare( "SELECT DATE_FORMAT(timestamp, '%%Y-%%m') AS month, COUNT(*) AS count FROM {$table_name} WHERE DATE_FORMAT(timestamp, '%%Y-%%m') IN ($placeholders) GROUP BY DATE_FORMAT(timestamp, '%%Y-%%m')", ...$months ) ); $counts = []; foreach ( $rows as $row ) { $counts[ $row->month ] = (int) $row->count; } foreach ( $months as $month ) { $chart_data[] = [ 'month' => gmdate( 'M', strtotime( $month . '-01' ) ), 'count' => isset( $counts[ $month ] ) ? $counts[ $month ] : 0, ]; } return $chart_data; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@includes/Api.php` at line 27, The get_volume_chart private helper in includes/Api/Metrics.php currently issues one $wpdb->get_var() per month in a loop; replace that with a single aggregated query: first build the 12-month list using gmdate and strtotime with current_time('timestamp'), create a placeholder list for $wpdb->prepare, run one SELECT DATE_FORMAT(timestamp, '%Y-%m') AS month, COUNT(*) AS count FROM {$table_name} WHERE DATE_FORMAT(timestamp, '%Y-%m') IN (...) GROUP BY DATE_FORMAT(timestamp, '%Y-%m'), then map rows into an associative counts array and iterate the original months list to produce chart_data entries with month (gmdate('M', strtotime($month.'-01'))) and count (0 when missing) and return chart_data.includes/Api/Metrics.php (1)
39-46: Silence PHPMD’s unused$requestwarning.If the parameter is intentionally unused, suppress the warning (or rename to
$_request) to keep static analysis clean.🔧 Optional PHPMD suppression
- * `@return` WP_Rest_Response|WP_Error + * `@return` WP_Rest_Response|WP_Error + * `@SuppressWarnings`(PHPMD.UnusedFormalParameter) */ public function get_metrics( $request ) {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@includes/Api/Metrics.php` around lines 39 - 46, The get_metrics method currently declares an unused parameter $request which triggers PHPMD; fix by either renaming the parameter to $_request (or $_unusedRequest) to signal intentional unusedness, or add a PHPMD suppression annotation on the method (e.g., a `@SuppressWarnings`("PHPMD.UnusedFormalParameter") docblock) so static analysis stops flagging get_metrics($request).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@includes/Api.php`:
- Line 27: The month iteration in Api\Metrics::get_volume_chart() and the
$last_month calculation in get_metrics() use 30*DAY_IN_SECONDS which causes
duplicate/missing months; replace that arithmetic with calendar-month operations
(e.g., use strtotime("-{$i} months", $baseDate) or DateTime::modify("-{$i}
months") and normalize to the first day of each month) so each loop iteration
produces distinct month boundaries; update both get_volume_chart() and
get_metrics() to compute the month label and start/end timestamps via
calendar-aware functions and ensure the loop uses the month index (i) rather
than fixed 30-day offsets.
In `@includes/Api/Metrics.php`:
- Around line 51-52: The current calculation uses a fixed 30‑day offset
(current_time + gmdate with DAY_IN_SECONDS) which can produce incorrect calendar
months; replace that logic for $current_month and $last_month with
calendar-month arithmetic (e.g. use PHP DateTime/DateInterval or strtotime with
"first day of previous month"/"last month" semantics, then format with 'Y-m') so
you always get the previous calendar month, and apply the same change to the
other occurrences around the $... calculations at the 107-111 area; target
symbols: $current_month, $last_month, current_time, gmdate, DAY_IN_SECONDS.
In `@includes/Gateways/Plivo.php`:
- Around line 114-117: The return value currently assigns $body->message_uuid
directly to 'reference_id' but Plivo returns message_uuid as an array, causing
type mismatch for Dispatcher::log_sms(string $reference_id); update the code in
includes/Gateways/Plivo.php (the method that builds this return) to detect if
$body->message_uuid is an array and, if so, extract the first element (or null
if empty), otherwise cast to string; ensure 'reference_id' is always a string or
null before returning so it matches Dispatcher::log_sms's expected type.
In `@includes/Install.php`:
- Around line 40-48: The schema defines created_at and updated_at but
Dispatcher::log_sms inserts a non-existent timestamp column and never supplies
updated_at; update Dispatcher::log_sms to insert into the actual columns
(created_at and updated_at) rather than timestamp, and ensure updated_at gets a
value (e.g., set updated_at = created_at or current time) so the $wpdb->insert()
matches the Install.php table definition; adjust the array keys and values in
Dispatcher::log_sms and verify the columns used in any other $wpdb->insert()
calls reference created_at/updated_at, or alternatively modify Install.php to
add a timestamp column and make updated_at nullable/default CURRENT_TIMESTAMP if
you prefer the schema change.
- Around line 14-21: The installer is calling create_tables() twice and contains
leftover debug error_log() calls; remove the unconditional first call to
create_tables() so that create_tables() is only invoked inside the if (!
$installed) block (which also calls update_option('texty_installed')), and
delete any error_log(...) debug lines related to the installer to avoid noisy
logs (references: create_tables(), update_option('texty_installed'), and
error_log()).
- Line 40: Remove the "IF NOT EXISTS" clause from the CREATE TABLE SQL string so
dbDelta() can correctly parse the table name; locate the SQL assignment that
builds "$sql = \"CREATE TABLE IF NOT EXISTS {$table_name} ( ...\" (referencing
$sql and $table_name) and change it to "CREATE TABLE {$table_name} (...)" before
calling dbDelta() so schema diffs and updates are detected properly by
dbDelta().
In `@src/components/QuickSend.js`:
- Around line 39-42: The catch block in the QuickSend component currently only
resets setIsSending(false) and console.logs the error, so users get no feedback
on network/gateway failures; update the catch handler where apiFetch is called
(inside QuickSend's send flow / sendMessage handler) to call toast.error(...)
with a user-friendly message (e.g., "Failed to send message. Please try again.")
and include the error detail in the log or as part of the toast for debugging,
while still ensuring setIsSending(false) runs; reference the apiFetch call, the
catch callback, setIsSending, and toast.error when making the change.
In `@src/components/StatCard.js`:
- Around line 21-24: Wrap the hardcoded indicator labels in the translation
helper: change the JSX that renders {indicator === 'connected' ? 'Active' :
'Inactive'} inside the StatCard component to use the __('...') function for both
branches so they are localized; update the span rendering where the indicator
variable is used (the texty-stat-badge block) to call __('Active') and
__('Inactive') instead of raw strings and ensure the translation helper is
imported/available in this module if not already.
In `@src/components/VolumeChart.js`:
- Around line 1-9: The project imports Recharts components (BarChart, Bar,
XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer) in VolumeChart.js and
icons from lucide-react in StatCard.js and HelpResources.js but neither
"recharts" nor "lucide-react" are declared in package.json; add both packages to
the dependencies in package.json (choose appropriate semver ranges or latest
stable versions), then run your package manager (npm install or yarn install) to
update lockfile and ensure the imports resolve during build and tests.
In `@src/pages/Dashboard.js`:
- Around line 73-78: The subLabel currently treats any non-positive
metrics.usage_change as "No change", which hides decreases; update the StatCard
subLabel logic in Dashboard.js to handle three cases: positive (prefix with +
and "from last month"), negative (format as e.g. "-X.X% decrease from last
month" or localised equivalent), and zero (keep "No change from last month");
modify the expression that sets subLabel for the StatCard (where title is
"Monthly Usage" and props include value={formatUsage(metrics.monthly_usage)} and
loading={isLoading}) to inspect metrics.usage_change and render the appropriate
signed percentage text for negative values.
---
Duplicate comments:
In `@includes/Api/Tools.php`:
- Around line 64-68: The SMS-success logging block in Tools.php duplicates the
logic in Send.php; replace the inline check and call to Dispatcher::log_sms(...)
with a single call to the centralized helper Dispatcher::maybe_log_sms($to,
$status) (created per the Send.php review), and ensure maybe_log_sms implements
the status validation (is_wp_error, is_array, isset success) and calls
Dispatcher::log_sms($to, $reference_id) internally so this file no longer
repeats the conditional logic.
In `@includes/Dispatcher.php`:
- Around line 87-104: In log_sms() the insert uses a non-existent 'timestamp'
column and omits the NOT NULL 'updated_at' column defined in
Install.php::create_tables(), causing inserts to fail; replace the 'timestamp'
key with 'created_at' and also provide an 'updated_at' value (e.g., same
current_time('mysql') value) in the $data array, and call $wpdb->insert(
$table_name, $data, $format ) with an appropriate $format array to match column
types for explicitness and type safety.
---
Nitpick comments:
In `@includes/Api.php`:
- Line 27: The get_volume_chart private helper in includes/Api/Metrics.php
currently issues one $wpdb->get_var() per month in a loop; replace that with a
single aggregated query: first build the 12-month list using gmdate and
strtotime with current_time('timestamp'), create a placeholder list for
$wpdb->prepare, run one SELECT DATE_FORMAT(timestamp, '%Y-%m') AS month,
COUNT(*) AS count FROM {$table_name} WHERE DATE_FORMAT(timestamp, '%Y-%m') IN
(...) GROUP BY DATE_FORMAT(timestamp, '%Y-%m'), then map rows into an
associative counts array and iterate the original months list to produce
chart_data entries with month (gmdate('M', strtotime($month.'-01'))) and count
(0 when missing) and return chart_data.
In `@includes/Api/Metrics.php`:
- Around line 39-46: The get_metrics method currently declares an unused
parameter $request which triggers PHPMD; fix by either renaming the parameter to
$_request (or $_unusedRequest) to signal intentional unusedness, or add a PHPMD
suppression annotation on the method (e.g., a
`@SuppressWarnings`("PHPMD.UnusedFormalParameter") docblock) so static analysis
stops flagging get_metrics($request).
In `@includes/Api/Send.php`:
- Around line 64-75: Extract the duplicated SMS-success logging into a shared
helper on Dispatcher and replace both inline blocks with calls to it: add a
public static function maybe_log_sms($to, $status) on class Dispatcher that
checks ! is_wp_error($status), is_array($status) and !
empty($status['success']), pulls $reference_id if set, then calls
self::log_sms($to, $reference_id); update the call sites in Send.php and
Tools.php to call Dispatcher::maybe_log_sms($to, $status) instead of duplicating
the conditional logging logic.
In `@includes/Gateways/Clickatell.php`:
- Around line 132-135: The current return hardcodes 'reference_id' => null in
the Clickatell gateway; update the send/response-handling logic in the
Clickatell class to parse the provider response body (typically JSON) when a 202
is received and extract the message identifier (e.g., look for a top-level
"messages" array and use messages[0].id or messages[0].messageId, or fallback to
any provider-specific id field) and return it as 'reference_id' instead of null;
also include a sensible fallback (e.g., check response headers like X-Message-Id
or return null only if no id found) so the returned array mirrors other
gateways' use of the provider message ID.
In `@includes/Gateways/Fake.php`:
- Around line 7-11: The class docblock incorrectly labels this as "Twilio
Class"—update the docblock above the Fake gateway class (class name Fake) to
accurately describe this gateway (e.g., "Fake Gateway" or "Fake SMS gateway")
and adjust or remove the Twilio-specific `@see` URL so the comment matches the
implementation and intent.
In `@src/app.css`:
- Around line 371-377: The .texty-quick-send-form .button-primary rule uses
amber (`#f59e0b`) which is inconsistent with the project's brand green; update the
CSS selector .texty-quick-send-form .button-primary to use the brand green
(`#16a34a`) for background-color and border (keep color: white and existing
width/padding), so the quick-send button matches other interactive elements like
stat card icons/Header active links/progress fill/help card hover.
In `@src/components/HelpResources.js`:
- Around line 5-30: The resources array is recreated on each render; move the
const resources = [...] definition out of the component function to module scope
so it is allocated once (keep the same identifier resources and the existing
item keys like id/title/description/url/buttonText/icon), leaving the component
to reference resources without changes; keep the __() calls in place (they are
safe at module level) and ensure no props/state are referenced inside resources
after the move.
In `@src/components/VolumeChart.js`:
- Around line 35-41: The YAxis in VolumeChart.js is hardcoded to domain={[0,
10000]} and static ticks, so it won’t scale to real data; update the YAxis
(component name: YAxis in VolumeChart) to use an adaptive upper bound (e.g.,
domain={[0, 'auto']}) and remove or compute static ticks so Recharts will scale
the axis to the data (or compute ticks from the dataset inside the VolumeChart
component before rendering) ensuring bars don’t clip or sit near zero.
ℹ️ Review info
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (20)
includes/Api.phpincludes/Api/Metrics.phpincludes/Api/Send.phpincludes/Api/Tools.phpincludes/Dispatcher.phpincludes/Gateways/Clickatell.phpincludes/Gateways/Fake.phpincludes/Gateways/Plivo.phpincludes/Gateways/Twilio.phpincludes/Gateways/Vonage.phpincludes/Install.phpsrc/App.jssrc/app.csssrc/components/Header.jssrc/components/HelpResources.jssrc/components/QuickSend.jssrc/components/StatCard.jssrc/components/VolumeChart.jssrc/index.jssrc/pages/Dashboard.js
| return [ | ||
| 'success' => true, | ||
| 'reference_id' => isset( $body->message_uuid ) ? $body->message_uuid : null, | ||
| ]; |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# Find the Dispatcher class and log_sms method
find . -type f -name "*.php" | head -20Repository: getdokan/texty
Length of output: 609
🏁 Script executed:
# Search for Dispatcher::log_sms or log_sms definition
rg "log_sms" --type php -B 3 -A 10Repository: getdokan/texty
Length of output: 2135
🏁 Script executed:
# Find the Plivo.php file and examine surrounding context
rg "message_uuid" --type php -B 5 -A 5Repository: getdokan/texty
Length of output: 643
🏁 Script executed:
cat -n includes/Gateways/Plivo.phpRepository: getdokan/texty
Length of output: 5766
🌐 Web query:
Plivo SMS API send message response message_uuid format
💡 Result:
In Plivo’s Send Message (SMS/MMS/WhatsApp) API response, message_uuid is returned as a JSON array of UUID strings (even if you send to a single destination). Example: {"message_uuid":["db3ce55a-7f1d-11e1-8ea7-1231380bc196"]}. [1][2]
Each message_uuid value is a 36-character UUID (standard 8-4-4-4-12 with hyphens) that uniquely identifies a message detail record. [3]
Multiple recipients in one request will return multiple UUIDs in the message_uuid array. [2]
Sources: [1] [2] [3]
$body->message_uuid is a JSON array — reference_id will hold an array, not a string.
The Plivo send-message response returns "message_uuid" as an array of UUIDs (e.g., ["db3ce55a-7f1d-11e1-8ea7-1231380bc196"]), even for single recipients. json_decode() at line 108 (without the true flag) converts this to a PHP array, so $body->message_uuid is array, not string. Assigning it directly to 'reference_id' violates the type hint in Dispatcher::log_sms(), which declares @param string $reference_id. Storing an array in a VARCHAR/TEXT column will serialize it as the literal string "Array", breaking message tracking.
🐛 Proposed fix — extract first element
- return [
- 'success' => true,
- 'reference_id' => isset( $body->message_uuid ) ? $body->message_uuid : null,
- ];
+ return [
+ 'success' => true,
+ 'reference_id' => isset( $body->message_uuid[0] ) ? $body->message_uuid[0] : null,
+ ];📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| return [ | |
| 'success' => true, | |
| 'reference_id' => isset( $body->message_uuid ) ? $body->message_uuid : null, | |
| ]; | |
| return [ | |
| 'success' => true, | |
| 'reference_id' => isset( $body->message_uuid[0] ) ? $body->message_uuid[0] : null, | |
| ]; |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@includes/Gateways/Plivo.php` around lines 114 - 117, The return value
currently assigns $body->message_uuid directly to 'reference_id' but Plivo
returns message_uuid as an array, causing type mismatch for
Dispatcher::log_sms(string $reference_id); update the code in
includes/Gateways/Plivo.php (the method that builds this return) to detect if
$body->message_uuid is an array and, if so, extract the first element (or null
if empty), otherwise cast to string; ensure 'reference_id' is always a string or
null before returning so it matches Dispatcher::log_sms's expected type.
| $this->create_tables(); | ||
| $installed = get_option( 'texty_installed' ); | ||
| error_log( 'Texty Installer: ' . ( $installed ? 'Already installed' : 'Running installer' ) ); | ||
|
|
||
| if ( ! $installed ) { | ||
| update_option( 'texty_installed', time() ); | ||
| $this->create_tables(); | ||
| } |
There was a problem hiding this comment.
create_tables() is called twice on first install; debug error_log calls left in.
Line 14 calls create_tables() unconditionally, and line 20 calls it again inside the ! $installed block. The second call is redundant. Also, the error_log statements on lines 16 and 37 should be removed before merging — they're debug artifacts.
♻️ Suggested fix
public function run() {
- $this->create_tables();
$installed = get_option( 'texty_installed' );
- error_log( 'Texty Installer: ' . ( $installed ? 'Already installed' : 'Running installer' ) );
if ( ! $installed ) {
update_option( 'texty_installed', time() );
$this->create_tables();
}
update_option( 'texty_version', TEXTY_VERSION );
}📝 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.
| $this->create_tables(); | |
| $installed = get_option( 'texty_installed' ); | |
| error_log( 'Texty Installer: ' . ( $installed ? 'Already installed' : 'Running installer' ) ); | |
| if ( ! $installed ) { | |
| update_option( 'texty_installed', time() ); | |
| $this->create_tables(); | |
| } | |
| $installed = get_option( 'texty_installed' ); | |
| if ( ! $installed ) { | |
| update_option( 'texty_installed', time() ); | |
| $this->create_tables(); | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@includes/Install.php` around lines 14 - 21, The installer is calling
create_tables() twice and contains leftover debug error_log() calls; remove the
unconditional first call to create_tables() so that create_tables() is only
invoked inside the if (! $installed) block (which also calls
update_option('texty_installed')), and delete any error_log(...) debug lines
related to the installer to avoid noisy logs (references: create_tables(),
update_option('texty_installed'), and error_log()).
| error_log ( "Creating table with charset: {$charset_collate}" ); | ||
| $table_name = $wpdb->prefix . 'texty_sms_stat'; | ||
|
|
||
| $sql = "CREATE TABLE IF NOT EXISTS {$table_name} ( |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
WordPress dbDelta IF NOT EXISTS compatibility
💡 Result:
dbDelta() is not reliably compatible with MySQL’s CREATE TABLE IF NOT EXISTS syntax. Its SQL parser can mis-detect the table name (breaking the internal DESCRIBE checks / diff logic), which can lead to incorrect behavior. [1] It’s a known longstanding issue/limitation in core. [2]
Recommended patterns:
- Use plain
CREATE TABLE ...withdbDelta()(noIF NOT EXISTS).dbDelta()is designed to create the table if missing and alter it if it exists, so the clause is unnecessary and can confuse parsing. [3] - If you specifically want an “IF NOT EXISTS”-style guard, use
maybe_create_table()(WordPress checks for the table first instead of relying on MySQL’sIF NOT EXISTS). [4]
Practical workaround (common):
maybe_create_table( $table, $create_sql_without_IF_NOT_EXISTS )to ensure the table exists, thendbDelta( $create_sql_without_IF_NOT_EXISTS )for schema updates (columns/indexes).
Sources: [1] [2] [3] [4]
Remove IF NOT EXISTS from CREATE TABLE — incompatible with dbDelta().
WordPress dbDelta() has known parsing issues with the IF NOT EXISTS clause. Its SQL parser can fail to correctly detect the table name, breaking the diff logic and causing schema updates to be skipped on subsequent runs. dbDelta() is designed to create the table if missing, making the clause redundant.
Fix
- $sql = "CREATE TABLE IF NOT EXISTS {$table_name} (
+ $sql = "CREATE TABLE {$table_name} (📝 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.
| $sql = "CREATE TABLE IF NOT EXISTS {$table_name} ( | |
| $sql = "CREATE TABLE {$table_name} ( |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@includes/Install.php` at line 40, Remove the "IF NOT EXISTS" clause from the
CREATE TABLE SQL string so dbDelta() can correctly parse the table name; locate
the SQL assignment that builds "$sql = \"CREATE TABLE IF NOT EXISTS
{$table_name} ( ...\" (referencing $sql and $table_name) and change it to
"CREATE TABLE {$table_name} (...)" before calling dbDelta() so schema diffs and
updates are detected properly by dbDelta().
| .catch((err) => { | ||
| setIsSending(false); | ||
| console.log(err); | ||
| // toast.error(err.message); | ||
| }); |
There was a problem hiding this comment.
Silent failure: no user-facing error message in the catch block.
apiFetch rejects the promise for any non-2xx response, making this catch the sole handler for network errors and gateway failures. Without a toast.error() call here, the button simply resets from "Sending…" to "Send Message" with no feedback.
🐛 Proposed fix
.catch((err) => {
setIsSending(false);
console.log(err);
+ toast.error(err.message || __('An error occurred. Please try again.', 'texty'));
});📝 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.
| .catch((err) => { | |
| setIsSending(false); | |
| console.log(err); | |
| // toast.error(err.message); | |
| }); | |
| .catch((err) => { | |
| setIsSending(false); | |
| console.log(err); | |
| toast.error(err.message || __('An error occurred. Please try again.', 'texty')); | |
| }); |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/components/QuickSend.js` around lines 39 - 42, The catch block in the
QuickSend component currently only resets setIsSending(false) and console.logs
the error, so users get no feedback on network/gateway failures; update the
catch handler where apiFetch is called (inside QuickSend's send flow /
sendMessage handler) to call toast.error(...) with a user-friendly message
(e.g., "Failed to send message. Please try again.") and include the error detail
in the log or as part of the toast for debugging, while still ensuring
setIsSending(false) runs; reference the apiFetch call, the catch callback,
setIsSending, and toast.error when making the change.
| {indicator && ( | ||
| <span className={`texty-stat-badge texty-stat-badge--${indicator}`}> | ||
| {indicator === 'connected' ? 'Active' : 'Inactive'} | ||
| </span> |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
cat -n src/components/StatCard.jsRepository: getdokan/texty
Length of output: 2671
🏁 Script executed:
# Check what other components import from `@wordpress/i18n`
rg "from '@wordpress/i18n'" --type js -A 2Repository: getdokan/texty
Length of output: 1974
🏁 Script executed:
# Check if __ is already used in other components for similar labels
rg "__\(" src/components/ --type js -B 2 -A 2Repository: getdokan/texty
Length of output: 9425
Localize indicator labels.
The hardcoded "Active" and "Inactive" strings on line 23 should be wrapped with the __() function for translation support, consistent with the rest of the codebase.
Suggested localization update
import { Fragment } from 'react';
+import { __ } from '@wordpress/i18n';
import { Activity, BarChart3, TrendingUp } from 'lucide-react';- {indicator === 'connected' ? 'Active' : 'Inactive'}
+ {indicator === 'connected' ? __('Active', 'texty') : __('Inactive', 'texty')}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/components/StatCard.js` around lines 21 - 24, Wrap the hardcoded
indicator labels in the translation helper: change the JSX that renders
{indicator === 'connected' ? 'Active' : 'Inactive'} inside the StatCard
component to use the __('...') function for both branches so they are localized;
update the span rendering where the indicator variable is used (the
texty-stat-badge block) to call __('Active') and __('Inactive') instead of raw
strings and ensure the translation helper is imported/available in this module
if not already.
There was a problem hiding this comment.
Actionable comments posted: 3
♻️ Duplicate comments (2)
includes/Install.php (1)
37-37:⚠️ Potential issue | 🟠 MajorRemove
IF NOT EXISTSfrom thedbDelta()statement.Line 37 still uses
CREATE TABLE IF NOT EXISTS ...;dbDelta()can misparse this and skip schema diffs.🐛 Suggested fix
- $sql = "CREATE TABLE IF NOT EXISTS {$table_name} ( + $sql = "CREATE TABLE {$table_name} (🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@includes/Install.php` at line 37, The SQL used to create the table is built into the $sql variable and passed to dbDelta(), but it still contains "IF NOT EXISTS" which can cause dbDelta() to misparse and skip schema changes; remove the "IF NOT EXISTS" clause from the CREATE TABLE statement (the string assigned to $sql that uses {$table_name}) so dbDelta() receives a standard CREATE TABLE definition and can correctly apply schema diffs.includes/Api/Metrics.php (1)
52-53:⚠️ Potential issue | 🟠 MajorUse calendar-month arithmetic instead of 30-day offsets.
Line 53 and Line 151 use 30-day subtraction; that can skip or repeat months around month-length boundaries and skew
monthly_usage/volume_chart.🗓️ Suggested fix
- $current_month = current_time( 'Y-m' ); - $last_month = gmdate( 'Y-m', current_time( 'timestamp' ) - 30 * DAY_IN_SECONDS ); + $current = new \DateTimeImmutable( 'first day of this month', wp_timezone() ); + $current_month = $current->format( 'Y-m' ); + $last_month = $current->modify( '-1 month' )->format( 'Y-m' ); ... - for ( $i = 11; $i >= 0; $i-- ) { - $timestamp = current_time( 'timestamp' ) - ( $i * 30 * DAY_IN_SECONDS ); - $months[] = gmdate( 'Y-m', $timestamp ); - } + $current = new \DateTimeImmutable( 'first day of this month', wp_timezone() ); + for ( $i = 11; $i >= 0; $i-- ) { + $months[] = $current->modify( "-{$i} months" )->format( 'Y-m' ); + }Also applies to: 150-152
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@includes/Api/Metrics.php` around lines 52 - 53, Replace the 30-day offset arithmetic used to compute $last_month (and the similar code used for monthly_usage/volume_chart) with calendar-month arithmetic: use current_time('timestamp') as the base, create a DateTime/DateTimeImmutable (or use strtotime with relative strings) to compute the first day of the current month and then subtract one calendar month (e.g. "first day of last month") to produce the previous month; format the result with gmdate('Y-m', $timestamp) or DateTime::format and update the variables $current_month and $last_month and the other occurrences so month boundaries use calendar months rather than 30-day offsets.
🧹 Nitpick comments (2)
includes/Install.php (1)
37-45: Add index coverage for metrics read paths.The new metrics queries heavily filter by
created_atandstatus. Without indexes, this table will become a scan hotspot as SMS volume grows.📈 Suggested schema improvement
$sql = "CREATE TABLE {$table_name} ( id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, receiver VARCHAR(20) NOT NULL, gateway VARCHAR(50) NOT NULL, status VARCHAR(20) DEFAULT NULL, created_at DATETIME NOT NULL, updated_at DATETIME NOT NULL, - reference_id VARCHAR(100) DEFAULT NULL + reference_id VARCHAR(100) DEFAULT NULL, + KEY idx_created_at (created_at), + KEY idx_status_created_at (status, created_at) ) {$charset_collate};";🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@includes/Install.php` around lines 37 - 45, The CREATE TABLE statement built in $sql (in Install.php) lacks indexes for the metrics read paths that filter by created_at and status; add a composite index such as INDEX idx_status_created_at (status, created_at) to the table definition (and optionally a standalone INDEX idx_created_at (created_at) if you expect many range-only queries) so queries filtering/sorting by status and created_at use indexes instead of full table scans.includes/Api/Metrics.php (1)
56-71: AvoidDATE_FORMAT(created_at, ...)filters and per-month query loops.This query shape is index-unfriendly and adds avoidable request latency (12 separate COUNTs). Prefer range predicates and one grouped query for the 12-month series.
Also applies to: 155-162
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@includes/Api/Metrics.php` around lines 56 - 71, The current COUNT queries for $monthly_usage and $last_month_usage use DATE_FORMAT(created_at, ...) which is index-unfriendly and repeats per-month queries; replace these by using date range predicates on created_at (e.g., >= month_start AND < next_month_start) in the $wpdb->prepare calls and refactor the 12-month loop into a single grouped query that SELECTs COUNT(*) GROUP BY DATE_FORMAT(created_at, '%Y-%m') or better yet GROUP BY YEAR(created_at), MONTH(created_at) with appropriate range bounds to return the 12-month series in one call; update the code paths that set $monthly_usage, $last_month_usage and the 12-month collection to consume results from that single grouped query and preserve the status = 'sent' filter.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@includes/Dispatcher.php`:
- Line 126: The $wpdb->insert call can fail and is currently ignored; update the
code in Dispatcher.php to check the return value of $wpdb->insert($table_name,
$data) and handle failures explicitly: if the insert returns false, capture
$wpdb->last_error (and/or $wpdb->print_error()), log the error with context
(include $table_name and $data identifiers), and return or surface a failure
(e.g., return false or a WP_Error) so callers know the SMS event wasn't
persisted and metrics can be corrected; ensure you reference the same
$wpdb->insert, $table_name and $data symbols and add no-op behavior only when
insert succeeds.
- Around line 100-112: The current status logic sets $status =
is_wp_error($result) ? 'failed' : 'sent', which misclassifies falsey or explicit
failure responses as 'sent'; update the determination of $status (where $status
and $result are used just before the reference_id extraction block) to mark as
'failed' when is_wp_error($result) OR $result === false OR (is_array($result) &&
isset($result['success']) && !$result['success']) OR empty($result); otherwise
set 'sent', leaving the reference_id extraction code (checking ['sid'],
['message-id'], ['message_uuid']) unchanged.
In `@package.json`:
- Line 25: The dependency "@wedevs/plugin-ui" is referenced as a floating Git
URL; update the package.json entry for "@wedevs/plugin-ui" to pin it to an
immutable revision by appending a tag or commit SHA (e.g.
git+https://github.com/mrabbani/plugin-ui.git#v1.2.3 or #<commit-sha>), then
reinstall/update the lockfile so the lock reflects the pinned revision; ensure
the package name "@wedevs/plugin-ui" is the one you change and commit the
updated package.json and lockfile.
---
Duplicate comments:
In `@includes/Api/Metrics.php`:
- Around line 52-53: Replace the 30-day offset arithmetic used to compute
$last_month (and the similar code used for monthly_usage/volume_chart) with
calendar-month arithmetic: use current_time('timestamp') as the base, create a
DateTime/DateTimeImmutable (or use strtotime with relative strings) to compute
the first day of the current month and then subtract one calendar month (e.g.
"first day of last month") to produce the previous month; format the result with
gmdate('Y-m', $timestamp) or DateTime::format and update the variables
$current_month and $last_month and the other occurrences so month boundaries use
calendar months rather than 30-day offsets.
In `@includes/Install.php`:
- Line 37: The SQL used to create the table is built into the $sql variable and
passed to dbDelta(), but it still contains "IF NOT EXISTS" which can cause
dbDelta() to misparse and skip schema changes; remove the "IF NOT EXISTS" clause
from the CREATE TABLE statement (the string assigned to $sql that uses
{$table_name}) so dbDelta() receives a standard CREATE TABLE definition and can
correctly apply schema diffs.
---
Nitpick comments:
In `@includes/Api/Metrics.php`:
- Around line 56-71: The current COUNT queries for $monthly_usage and
$last_month_usage use DATE_FORMAT(created_at, ...) which is index-unfriendly and
repeats per-month queries; replace these by using date range predicates on
created_at (e.g., >= month_start AND < next_month_start) in the $wpdb->prepare
calls and refactor the 12-month loop into a single grouped query that SELECTs
COUNT(*) GROUP BY DATE_FORMAT(created_at, '%Y-%m') or better yet GROUP BY
YEAR(created_at), MONTH(created_at) with appropriate range bounds to return the
12-month series in one call; update the code paths that set $monthly_usage,
$last_month_usage and the 12-month collection to consume results from that
single grouped query and preserve the status = 'sent' filter.
In `@includes/Install.php`:
- Around line 37-45: The CREATE TABLE statement built in $sql (in Install.php)
lacks indexes for the metrics read paths that filter by created_at and status;
add a composite index such as INDEX idx_status_created_at (status, created_at)
to the table definition (and optionally a standalone INDEX idx_created_at
(created_at) if you expect many range-only queries) so queries filtering/sorting
by status and created_at use indexes instead of full table scans.
includes/Dispatcher.php
Outdated
| $status = is_wp_error( $result ) ? 'failed' : 'sent'; | ||
|
|
||
| // Extract reference_id from result | ||
| $reference_id = null; | ||
| if ( is_array( $result ) ) { | ||
| // Try different key names used by various gateways | ||
| if ( isset( $result['sid'] ) ) { | ||
| $reference_id = $result['sid']; | ||
| } elseif ( isset( $result['message-id'] ) ) { | ||
| $reference_id = $result['message-id']; | ||
| } elseif ( isset( $result['message_uuid'] ) ) { | ||
| $reference_id = $result['message_uuid']; | ||
| } |
There was a problem hiding this comment.
Status logging can overcount sent SMS.
Line 100 marks every non-WP_Error result as sent. Failed but non-error responses (e.g., false or ['success' => false]) will be misclassified and skew metrics.
🐛 Suggested fix
- $status = is_wp_error( $result ) ? 'failed' : 'sent';
+ $status = 'failed';
+ if ( ! is_wp_error( $result ) ) {
+ if ( is_array( $result ) ) {
+ $status = ! empty( $result['success'] ) ? 'sent' : 'failed';
+ } elseif ( true === $result ) {
+ $status = 'sent';
+ }
+ }
// Extract reference_id from result
$reference_id = null;
if ( is_array( $result ) ) {
- // Try different key names used by various gateways
- if ( isset( $result['sid'] ) ) {
+ if ( isset( $result['reference_id'] ) ) {
+ $reference_id = $result['reference_id'];
+ } elseif ( isset( $result['sid'] ) ) {
$reference_id = $result['sid'];
} elseif ( isset( $result['message-id'] ) ) {
$reference_id = $result['message-id'];
} elseif ( isset( $result['message_uuid'] ) ) {
$reference_id = $result['message_uuid'];
}
}📝 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.
| $status = is_wp_error( $result ) ? 'failed' : 'sent'; | |
| // Extract reference_id from result | |
| $reference_id = null; | |
| if ( is_array( $result ) ) { | |
| // Try different key names used by various gateways | |
| if ( isset( $result['sid'] ) ) { | |
| $reference_id = $result['sid']; | |
| } elseif ( isset( $result['message-id'] ) ) { | |
| $reference_id = $result['message-id']; | |
| } elseif ( isset( $result['message_uuid'] ) ) { | |
| $reference_id = $result['message_uuid']; | |
| } | |
| $status = 'failed'; | |
| if ( ! is_wp_error( $result ) ) { | |
| if ( is_array( $result ) ) { | |
| $status = ! empty( $result['success'] ) ? 'sent' : 'failed'; | |
| } elseif ( true === $result ) { | |
| $status = 'sent'; | |
| } | |
| } | |
| // Extract reference_id from result | |
| $reference_id = null; | |
| if ( is_array( $result ) ) { | |
| if ( isset( $result['reference_id'] ) ) { | |
| $reference_id = $result['reference_id']; | |
| } elseif ( isset( $result['sid'] ) ) { | |
| $reference_id = $result['sid']; | |
| } elseif ( isset( $result['message-id'] ) ) { | |
| $reference_id = $result['message-id']; | |
| } elseif ( isset( $result['message_uuid'] ) ) { | |
| $reference_id = $result['message_uuid']; | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@includes/Dispatcher.php` around lines 100 - 112, The current status logic
sets $status = is_wp_error($result) ? 'failed' : 'sent', which misclassifies
falsey or explicit failure responses as 'sent'; update the determination of
$status (where $status and $result are used just before the reference_id
extraction block) to mark as 'failed' when is_wp_error($result) OR $result ===
false OR (is_array($result) && isset($result['success']) && !$result['success'])
OR empty($result); otherwise set 'sent', leaving the reference_id extraction
code (checking ['sid'], ['message-id'], ['message_uuid']) unchanged.
| 'reference_id' => $reference_id, | ||
| ]; | ||
|
|
||
| $wpdb->insert( $table_name, $data ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery |
There was a problem hiding this comment.
Handle $wpdb->insert() failures explicitly.
Line 126 ignores insert failure. If DB writes fail, SMS events are silently lost and dashboard metrics drift.
🛠️ Suggested fix
- $wpdb->insert( $table_name, $data ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery
+ $inserted = $wpdb->insert( $table_name, $data ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery
+ if ( false === $inserted ) {
+ do_action( 'texty_sms_log_insert_failed', $wpdb->last_error, $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.
| $wpdb->insert( $table_name, $data ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery | |
| $inserted = $wpdb->insert( $table_name, $data ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery | |
| if ( false === $inserted ) { | |
| do_action( 'texty_sms_log_insert_failed', $wpdb->last_error, $data ); | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@includes/Dispatcher.php` at line 126, The $wpdb->insert call can fail and is
currently ignored; update the code in Dispatcher.php to check the return value
of $wpdb->insert($table_name, $data) and handle failures explicitly: if the
insert returns false, capture $wpdb->last_error (and/or $wpdb->print_error()),
log the error with context (include $table_name and $data identifiers), and
return or surface a failure (e.g., return false or a WP_Error) so callers know
the SMS event wasn't persisted and metrics can be corrected; ensure you
reference the same $wpdb->insert, $table_name and $data symbols and add no-op
behavior only when insert succeeds.
| "react-select": "^5.8.0", | ||
| "react-toastify": "^6.2.0" | ||
| "react-toastify": "^6.2.0", | ||
| "@wedevs/plugin-ui": "git+https://github.com/mrabbani/plugin-ui.git" |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# Read the package.json file to see the current state
cat -n package.json | head -40Repository: getdokan/texty
Length of output: 1531
🌐 Web query:
github.com/mrabbani/plugin-ui commits tags releases
💡 Result:
- Repo:
github.com/mrabbani/plugin-uiredirects togetdokan/plugin-ui. [1] - Commits: 187 commits on
main. [1]- Latest commit activity shown: Feb 19, 2026 (e.g., “Merge pull request
#54…”). [2]
- Latest commit activity shown: Feb 19, 2026 (e.g., “Merge pull request
- Releases: No GitHub Releases published (“There aren’t any releases here”). [3]
- Tags: GitHub’s
/tagsview (in this crawl) does not show any tag list—it appears there may be no tags, but I can’t fully confirm without the GitHub API / a git fetch. [3]
Sources: [1] [2] [3]
Pin @wedevs/plugin-ui to an immutable revision.
The Git dependency on line 25 lacks a commit SHA or tag reference, meaning builds will resolve to whatever is the latest commit on the default branch. This creates non-deterministic builds and weakens supply-chain reproducibility.
Suggest pinning to a specific commit or tag:
♻️ Suggested fix
- "@wedevs/plugin-ui": "git+https://github.com/mrabbani/plugin-ui.git"
+ "@wedevs/plugin-ui": "git+https://github.com/mrabbani/plugin-ui.git#<commit-sha>"🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@package.json` at line 25, The dependency "@wedevs/plugin-ui" is referenced as
a floating Git URL; update the package.json entry for "@wedevs/plugin-ui" to pin
it to an immutable revision by appending a tag or commit SHA (e.g.
git+https://github.com/mrabbani/plugin-ui.git#v1.2.3 or #<commit-sha>), then
reinstall/update the lockfile so the lock reflects the pinned revision; ensure
the package name "@wedevs/plugin-ui" is the one you change and commit the
updated package.json and lockfile.
There was a problem hiding this comment.
Actionable comments posted: 2
♻️ Duplicate comments (1)
src/components/QuickSend.js (1)
40-43:⚠️ Potential issue | 🟡 MinorSurface API failures to users in the
catchpath.Line [40]-[43] only resets loading and logs to console; users get no feedback on network/request failures.
🐛 Proposed fix
.catch((err) => { setIsSending(false); - console.log(err); + console.error(err); + toast.error( + err?.message || __('Failed to send message. Please try again.', 'texty') + ); });🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/QuickSend.js` around lines 40 - 43, The catch block currently only calls setIsSending(false) and console.log(err) which leaves users unaware of failures; update the catch in the QuickSend request path to surface the error to the UI by passing the error message onward — either call an existing error handler prop (e.g., onError(err)) if QuickSend accepts one, or set a component error state (e.g., setErrorMessage(err.message || String(err))) and ensure the component renders that state or triggers a toast/notification; keep the existing setIsSending(false) and include the actual err.message in the user-visible message.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@includes/Api/Metrics.php`:
- Line 59: Replace UTC-based gmdate() usage with site-local DateTimeImmutable
using wp_timezone() so metrics use the same timezone as created_at;
specifically, change the $current_day assignment (and the similar occurrences at
the other two locations) to construct a DateTimeImmutable('now', wp_timezone())
and extract the day via ->format('d') cast to int, ensuring all comparisons
(e.g., DAY(created_at) checks that feed usage_change, delivery_rate, and charts)
use the same timezone basis as Dispatcher.php's current_time('mysql').
In `@src/style.scss`:
- Line 468: Remove the duplicate stylesheet import
"@wedevs/plugin-ui/styles.css" from src/style.scss (the trailing `@import` at the
end of the file) because it violates the no-invalid-position-at-import-rule by
appearing after CSS rules and is redundant with the import already present in
src/index.js; simply delete the lone `@import` "@wedevs/plugin-ui/styles.css" line
at the end of style.scss.
---
Duplicate comments:
In `@src/components/QuickSend.js`:
- Around line 40-43: The catch block currently only calls setIsSending(false)
and console.log(err) which leaves users unaware of failures; update the catch in
the QuickSend request path to surface the error to the UI by passing the error
message onward — either call an existing error handler prop (e.g., onError(err))
if QuickSend accepts one, or set a component error state (e.g.,
setErrorMessage(err.message || String(err))) and ensure the component renders
that state or triggers a toast/notification; keep the existing
setIsSending(false) and include the actual err.message in the user-visible
message.
ℹ️ Review info
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (9)
includes/Api/Metrics.phpsrc/app.csssrc/components/Header.jssrc/components/HelpResources.jssrc/components/QuickSend.jssrc/components/VolumeChart.jssrc/index.jssrc/pages/Dashboard.jssrc/style.scss
✅ Files skipped from review due to trivial changes (1)
- src/app.css
🚧 Files skipped from review as they are similar to previous changes (3)
- src/components/Header.js
- src/components/VolumeChart.js
- src/components/HelpResources.js
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (2)
includes/Dispatcher.php (2)
116-116:⚠️ Potential issue | 🟠 MajorHandle
$wpdb->insert()failure to avoid silent data loss.Insert failures are silently ignored. If the DB write fails, SMS events are lost and dashboard metrics become inaccurate.
🛠️ Proposed fix
- $wpdb->insert( $table_name, $data ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery + $inserted = $wpdb->insert( $table_name, $data ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery + if ( false === $inserted ) { + do_action( 'texty_sms_log_insert_failed', $wpdb->last_error, $data ); + }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@includes/Dispatcher.php` at line 116, The $wpdb->insert($table_name, $data) call can fail silently; check its return value and handle failures by inspecting $wpdb->last_error and surface/log the failure so SMS events are not lost. Modify the function containing the $wpdb->insert call to capture the return (e.g. $result = $wpdb->insert(...)); if $result === false, log a descriptive error containing $wpdb->last_error plus context (table name $table_name and encoded $data), and then either return/throw an error or mark the event as failed so callers can handle it (preserve the phpcs ignore comment if needed). Ensure you reference the same $wpdb, $table_name and $data variables when implementing this check.
99-100:⚠️ Potential issue | 🟠 MajorStatus logic can misclassify failed sends as 'sent'.
Non-
WP_Errorfailures (e.g.,false,['success' => false]) will be marked as'sent', skewing delivery metrics.🐛 Proposed fix
// Determine status - $status = is_wp_error( $result ) ? 'failed' : 'sent'; + $status = 'failed'; + if ( ! is_wp_error( $result ) ) { + if ( is_array( $result ) && ! empty( $result['success'] ) ) { + $status = 'sent'; + } elseif ( true === $result ) { + $status = 'sent'; + } + }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@includes/Dispatcher.php` around lines 99 - 100, The current status assignment uses only is_wp_error($result) which will mark non-WP_Error failures (false, null, or arrays like ['success'=>false]) as 'sent'; update the logic in includes/Dispatcher.php (class Dispatcher, around the $status and $result handling) to treat any falsy result or an explicit failure indicator as 'failed' — e.g., set $status to 'failed' when is_wp_error($result) OR $result === false OR $result === null OR (is_array($result) && array_key_exists('success', $result) && !$result['success']); otherwise set 'sent'. Ensure you reference and update the $status assignment and any downstream code that relies on $status.
🧹 Nitpick comments (2)
includes/Dispatcher.php (1)
118-130: LGTM!The
extract_reference_id()helper cleanly handles multiple gateway response formats. The key list covers common providers (Twilio'ssid, Vonage'smessage-id, Plivo'smessage_uuid, Clickatell'sapiMsgId).Minor style nit: add a blank line after the method signature for consistency with other methods.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@includes/Dispatcher.php` around lines 118 - 130, The method extract_reference_id currently has no blank line after its signature; add a single blank line immediately after the function declaration (private function extract_reference_id( $result ) {) so the body starts with an empty line for consistency with the project's method formatting conventions; keep the function name and existing logic unchanged.includes/Install.php (1)
13-22: Minor: Consider adding index for query performance.Once the
dbDelta()call is added, consider adding an index oncreated_atandstatuscolumns sinceMetrics.phpqueries heavily filter by these fields withWHERE created_at >= ... AND status = 'sent'.📊 Schema with index
$sql = "CREATE TABLE {$table_name} ( id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, receiver VARCHAR(20) NOT NULL, gateway VARCHAR(50) NOT NULL, status VARCHAR(20) DEFAULT NULL, created_at DATETIME NOT NULL, updated_at DATETIME NOT NULL, - reference_id VARCHAR(100) DEFAULT NULL + reference_id VARCHAR(100) DEFAULT NULL, + KEY status_created (status, created_at) ) {$charset_collate};";🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@includes/Install.php` around lines 13 - 22, The table created in create_tables() needs an index on created_at and status to speed up the Metrics.php queries that use WHERE created_at >= ... AND status = 'sent'; update the SQL used in create_tables() (the string passed to dbDelta()) to add a composite index (or separate indexes) on (created_at, status) and then call dbDelta() so WordPress will create the index on existing installs; ensure the index name is unique (e.g., idx_created_at_status) and leave update_option('texty_version', TEXTY_VERSION) intact so migrations can be detected.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@includes/Install.php`:
- Around line 29-46: The create_tables() method builds $sql but never executes
it; update create_tables() so it constructs a dbDelta-compatible CREATE TABLE
statement (remove the "IF NOT EXISTS" from $sql), keep the existing require_once
ABSPATH . 'wp-admin/includes/upgrade.php' and $charset_collate usage, then call
dbDelta($sql) to actually create/update the table; refer to the create_tables()
function and the $sql variable so you remove IF NOT EXISTS and add the
dbDelta($sql) invocation after $sql is set.
---
Duplicate comments:
In `@includes/Dispatcher.php`:
- Line 116: The $wpdb->insert($table_name, $data) call can fail silently; check
its return value and handle failures by inspecting $wpdb->last_error and
surface/log the failure so SMS events are not lost. Modify the function
containing the $wpdb->insert call to capture the return (e.g. $result =
$wpdb->insert(...)); if $result === false, log a descriptive error containing
$wpdb->last_error plus context (table name $table_name and encoded $data), and
then either return/throw an error or mark the event as failed so callers can
handle it (preserve the phpcs ignore comment if needed). Ensure you reference
the same $wpdb, $table_name and $data variables when implementing this check.
- Around line 99-100: The current status assignment uses only
is_wp_error($result) which will mark non-WP_Error failures (false, null, or
arrays like ['success'=>false]) as 'sent'; update the logic in
includes/Dispatcher.php (class Dispatcher, around the $status and $result
handling) to treat any falsy result or an explicit failure indicator as 'failed'
— e.g., set $status to 'failed' when is_wp_error($result) OR $result === false
OR $result === null OR (is_array($result) && array_key_exists('success',
$result) && !$result['success']); otherwise set 'sent'. Ensure you reference and
update the $status assignment and any downstream code that relies on $status.
---
Nitpick comments:
In `@includes/Dispatcher.php`:
- Around line 118-130: The method extract_reference_id currently has no blank
line after its signature; add a single blank line immediately after the function
declaration (private function extract_reference_id( $result ) {) so the body
starts with an empty line for consistency with the project's method formatting
conventions; keep the function name and existing logic unchanged.
In `@includes/Install.php`:
- Around line 13-22: The table created in create_tables() needs an index on
created_at and status to speed up the Metrics.php queries that use WHERE
created_at >= ... AND status = 'sent'; update the SQL used in create_tables()
(the string passed to dbDelta()) to add a composite index (or separate indexes)
on (created_at, status) and then call dbDelta() so WordPress will create the
index on existing installs; ensure the index name is unique (e.g.,
idx_created_at_status) and leave update_option('texty_version', TEXTY_VERSION)
intact so migrations can be detected.
ℹ️ Review info
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
includes/Api/Metrics.phpincludes/Dispatcher.phpincludes/Install.phpsrc/style.scss
✅ Files skipped from review due to trivial changes (1)
- src/style.scss
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (1)
includes/Install.php (1)
37-45:⚠️ Potential issue | 🟠 MajorRemove
IF NOT EXISTSfrom thedbDelta()DDL.
dbDelta()can mis-parseCREATE TABLE IF NOT EXISTS ..., which can prevent future schema diffs from applying reliably.Suggested fix
- $sql = "CREATE TABLE IF NOT EXISTS {$table_name} ( + $sql = "CREATE TABLE {$table_name} ( id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, receiver VARCHAR(20) NOT NULL, gateway VARCHAR(50) NOT NULL, status VARCHAR(20) DEFAULT NULL, created_at DATETIME NOT NULL, updated_at DATETIME NOT NULL, reference_id VARCHAR(100) DEFAULT NULL ) {$charset_collate};";WordPress dbDelta CREATE TABLE syntax requirements: is "IF NOT EXISTS" recommended or known to break dbDelta table parsing/diff detection?🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@includes/Install.php` around lines 37 - 45, The CREATE TABLE statement assigned to $sql in includes/Install.php uses "CREATE TABLE IF NOT EXISTS", which breaks WordPress dbDelta() parsing; remove the "IF NOT EXISTS" token so the SQL reads "CREATE TABLE {$table_name} (... ) {$charset_collate};" (leave all columns, $table_name and $charset_collate intact) before calling dbDelta() to ensure future schema diffs are detected correctly.
🧹 Nitpick comments (1)
includes/Install.php (1)
38-45: Add indexes for metrics query paths.Current schema lacks indexes aligned with dashboard reads (
status,created_atfilters). This will degrade astexty_sms_statgrows.Suggested schema indexing
$sql = "CREATE TABLE {$table_name} ( id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, receiver VARCHAR(20) NOT NULL, gateway VARCHAR(50) NOT NULL, status VARCHAR(20) DEFAULT NULL, created_at DATETIME NOT NULL, updated_at DATETIME NOT NULL, - reference_id VARCHAR(100) DEFAULT NULL + reference_id VARCHAR(100) DEFAULT NULL, + KEY idx_created_at (created_at), + KEY idx_status_created_at (status, created_at) ) {$charset_collate};";🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@includes/Install.php` around lines 38 - 45, Add appropriate indexes to the texty_sms_stat table to support dashboard queries that filter by status and created_at: alter the CREATE TABLE statement in includes/Install.php (the block defining texty_sms_stat with columns id, receiver, gateway, status, created_at, updated_at, reference_id) to include an index on status and a composite index on (created_at, status) (or created_at alone if preferred) so reads for metrics/dashboards are efficient; update the schema string accordingly and ensure index names are unique (e.g., idx_texty_status, idx_texty_created_at_status).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@includes/Api/Metrics.php`:
- Line 196: Replace the DateTimeImmutable->format('M') usage with a call to
wp_date so month labels are localized; specifically, where the array sets
'month' (using new \DateTimeImmutable($month_key . '-01', wp_timezone())), pass
that date's timestamp (or the equivalent strtotime of "$month_key-01") into
wp_date('M', ...) with wp_timezone() so WordPress returns translated month
abbreviations for the dashboard output.
---
Duplicate comments:
In `@includes/Install.php`:
- Around line 37-45: The CREATE TABLE statement assigned to $sql in
includes/Install.php uses "CREATE TABLE IF NOT EXISTS", which breaks WordPress
dbDelta() parsing; remove the "IF NOT EXISTS" token so the SQL reads "CREATE
TABLE {$table_name} (... ) {$charset_collate};" (leave all columns, $table_name
and $charset_collate intact) before calling dbDelta() to ensure future schema
diffs are detected correctly.
---
Nitpick comments:
In `@includes/Install.php`:
- Around line 38-45: Add appropriate indexes to the texty_sms_stat table to
support dashboard queries that filter by status and created_at: alter the CREATE
TABLE statement in includes/Install.php (the block defining texty_sms_stat with
columns id, receiver, gateway, status, created_at, updated_at, reference_id) to
include an index on status and a composite index on (created_at, status) (or
created_at alone if preferred) so reads for metrics/dashboards are efficient;
update the schema string accordingly and ensure index names are unique (e.g.,
idx_texty_status, idx_texty_created_at_status).
| $chart_data = []; | ||
| foreach ( $months_map as $month_key => $count ) { | ||
| $chart_data[] = [ | ||
| 'month' => ( new \DateTimeImmutable( $month_key . '-01', wp_timezone() ) )->format( 'M' ), |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
WordPress wp_date() function localization month names compared to PHP DateTimeImmutable format M
💡 Result:
wp_date( 'M', … )is locale-aware: WordPress rewrites format chars likeM/F/D/lusingWP_Localetranslated month/weekday names (and can also apply genitive/declension rules viawp_maybe_decline_date()). [1] [3] [4]DateTimeImmutable->format('M')is not locale-aware: PHP definesMas a fixed “Jan … Dec” English abbreviation, regardless of locale. [2]
So if you want localized month abbreviations like WordPress produces, format via WordPress:
$dt = new DateTimeImmutable('2026-02-26 12:00:00', wp_timezone());
echo wp_date('M', $dt->getTimestamp(), $dt->getTimezone()); // localized "Feb" (or equivalent)If you instead need localization purely in PHP (outside WP’s locale system), use an ICU-based formatter (e.g., IntlDateFormatter), because format('M') won’t translate. [2]
Sources: [1] WordPress wp_date() reference, [2] PHP DateTimeInterface::format() reference, [3] WordPress WP_Locale reference, [4] WordPress wp_maybe_decline_date() reference.
🏁 Script executed:
head -n 200 includes/Api/Metrics.php | tail -n 20Repository: getdokan/texty
Length of output: 674
Use localized month labels for dashboard output.
Line 196 uses PHP DateTimeImmutable::format('M'), which returns English month abbreviations ("Jan", "Feb", etc.) regardless of site locale. For UI strings, use wp_date() instead, which respects the WordPress locale and applies translated month names.
Suggested fix
- 'month' => ( new \DateTimeImmutable( $month_key . '-01', wp_timezone() ) )->format( 'M' ),
+ 'month' => wp_date(
+ 'M',
+ ( new \DateTimeImmutable( $month_key . '-01', wp_timezone() ) )->getTimestamp(),
+ wp_timezone()
+ ),🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@includes/Api/Metrics.php` at line 196, Replace the
DateTimeImmutable->format('M') usage with a call to wp_date so month labels are
localized; specifically, where the array sets 'month' (using new
\DateTimeImmutable($month_key . '-01', wp_timezone())), pass that date's
timestamp (or the equivalent strtotime of "$month_key-01") into wp_date('M',
...) with wp_timezone() so WordPress returns translated month abbreviations for
the dashboard output.
Summary by CodeRabbit