Skip to content

Sync main to develop after v2.3.1 release - #2163

Merged
subodhr258 merged 1 commit into
developfrom
sync/main-to-develop-2.3.1
Oct 7, 2026
Merged

subodhr258 merged 1 commit into
developfrom
sync/main-to-develop-2.3.1

Conversation

@subodhr258

Copy link
Copy Markdown
Collaborator

Back-sync after the v2.3.1 release (rtCamp/godam-plugin-wp#46): brings the develop → main release merge (#2161) back onto develop.

Clean alignment: 1 merge commit, no file changes. Merge with a merge commit, not squash.

* Fix: Refresh What's New release post cache on every plugin update

Delete the rtgodam_release_data transient on every version bump,
patch releases included, so sites that cached an older release post
fetch the current one the next time What's New opens. Patch updates
still don't redirect to What's New.

Refs rtCamp/godam-plugin-wp#46

* Release prep v2.3.1: version bump and changelog

* Update POT reference for class-update.php line shift

* fix(analytics): send the media's job id with layer events

Both buffered layer-event flushes (the player bundle's in analytics.js and
the image-page runtime's in layer-analytics-runtime.js) sent `jobId: ''`
for any event keyed by the WordPress attachment id, which is every hotspot,
CTA, form, poll and Woo layer event on a WordPress video or image. The page
load event already sends the element's `data-job_id`. They now read it
from the same element the flush already finds for `data-block-source`.

The image frame had no job id to read, so render.php now puts the image's
job id on `.godam-image__frame` as `data-job_id`, from
rtgodam_get_job_id_by_attachment_id(). GoDAM for Woo's in-image add reads
that attribute from the same frame for its Assisted touch and order stamp.

Events stored without a job id are later rewritten by GoDAM Core's job-id
backfill, and summaries keyed on job_id have to follow that rewrite
(rtCamp/godam-analytics#344). Sending it up front leaves nothing to rewrite.
An empty `data-job_id` still sends no job_id, as before.

* fix(analytics): say revenue is after discounts, excluding tax, shipping and fees

The Revenue card and the Top Products "Revenue" column tooltips said only
"before refunds". The figure is the WooCommerce order line total
(WC_Order_Item_Product::get_total(), "Line total (after discounts)"), so
coupons are already taken off and tax, shipping and fees are not in it. A
merchant comparing it with their order totals saw a lower number with no
reason given. Both tooltips now say so, matching the Revenue Analytics docs
page.

The RevenueCard test that checked the "excluding N orders in other
currencies" line was absent by asserting the card contains no "excluding" at
all now matches that line's own wording, since the tooltip says "excluding
tax". New tests pin the wording in both tooltips. POT regenerated.

* fix(analytics): explain grouped and external products' empty figures in Top Products

A grouped product is never added to the cart: WooCommerce adds the
products it lists, and with rtCamp/godam-for-woo#223 those products get
the credit. An external product is bought on another site. Either way its
Add to Cart and Revenue stay empty, and the in-video hint said grouped
and external products convert on the product page, which is true of
neither.

The proxy now sends each product's WooCommerce type. The table greys a
grouped or external product's adds and revenue with a hint that says why,
and the in-video hint gives each type its own reason.

A product with adds but no times shown in the date range (bought from a
grouped product's page, added after a Reel Pop, or clicked before the
range) showed an add-to-cart rate of 0.0%, which reads as "shown, and
nobody added it". It now shows "-" with a hint.

* Use the site's own origin for analytics and engagement reads

Analytics events are recorded under the visitor's window.location.origin.
The analytics and engagement read routes now send this site's origin,
built on the server from home_url() by the new rtgodam_get_site_origin(),
instead of the site_url sent with the REST request. The now unused
site_url argument is removed from those routes; clients that still send
it are unaffected.

Adds SiteOriginTest, covering the helper and every read that forwards a
site URL.

* Regenerate godam.pot for moved line references

* Align analytics route permissions with the screens and with WooCommerce reports

Dashboard routes now ask for edit_pages, the capability the Dashboard menu
needs, instead of upload_files. dashboard-metrics, dashboard-history,
top-videos, top-products, placement-funnels, revenue-summary and video-funnel
move; fetch, history and layer-analytics stay on upload_files for the
Analytics page and the Video Editor.

Figures that come from orders (revenue, order counts, purchases,
Video-to-Purchase, the funnels' Purchase step and per-hotspot revenue) now
follow WooCommerce's report permission, view_woocommerce_reports, which
WooCommerce grants to shop managers and administrators. The routes leave those
fields out of their responses for everyone else, so they cannot be read through
the route. Views, plays, clicks and add-to-carts are unchanged.

The Dashboard and Analytics pages get a canViewRevenue flag and leave out the
Revenue card, Video-to-Purchase, revenue tips, the funnels' Purchase step, the
Top Products revenue column (and its CSV columns) and per-hotspot revenue.
Without the Purchase step the Purchase Funnel card reads as a Cart Funnel.

* Hide the GA4 widget's purchase count from users who cannot see order data

The Dashboard's GA4 widget shows how many Purchase events were prepared, a
count that comes from orders. It now follows the same rule as the other order
figures: only users with WooCommerce's report permission see it. Everyone else
still sees the Add to Cart count and the connection status.

* Fix the POT header bug-report URL so it matches CI's build

make-pot took the plugin slug from the worktree folder name, so the header read
support/plugin/analytics-route-permissions instead of support/plugin/godam. Regenerated
with the slug set; only that header line changes.

* Editors see video data only: store data needs WooCommerce's report permission

Everything that comes from the store (products, add-to-carts, Video-to-Cart,
orders, purchases, revenue) now follows view_woocommerce_reports, which
WooCommerce grants to shop managers and administrators. Editors and authors get
no WooCommerce or Products menu, so they see video data only.

Routes that are all store data (top-products, revenue-summary,
placement-funnels, video-funnel) need edit_pages and view_woocommerce_reports
and answer 403 otherwise. Routes that mix video and store data (dashboard-metrics,
fetch, layer-analytics) strip the store fields for everyone else: revenue,
orders, purchases, add-to-carts, Video-to-Cart, the funnel, the add-to-cart rows
of the per-layer counters and per-hotspot revenue. Views, plays, watch time and
hotspot clicks stay.

The screens hide the whole store section instead of trimming it: the Top
Products table (editors land on Top Videos), both funnels, the Revenue card,
Video-to-Cart, Video-to-Purchase, the GA4 box, the layer funnel's Added to Cart
bar and the layer panel's revenue. The localized flag is now canViewStoreData.

This replaces the earlier column-by-column hiding, so the funnel, Top Products
and GA4 widget components are back to their develop versions.

* Add a filter for the site origin used by analytics reads

home_url() and the address visitors load the site from can differ, for
example behind a proxy that ends TLS without telling WordPress, or when a
site answers on several domains. rtgodam_site_origin lets those sites
return the origin their visitors' browsers report. Values that are not a
bare scheme://host[:port] are ignored. The docblock states the assumption.

* fix(analytics): name variable products explicitly in the in-video hint

The hint fell through to the variable-product sentence for any restricted
product type other than grouped or external. It now checks for 'variable'
and gives any other product the proxy marks as not addable in a video, or
a row from before the proxy sent product_type, a neutral sentence that
says only what the flag proves.

* fix(analytics): send the store's UTC offset with the funnel reads

The analytics service counts the funnel, Video-to-Cart, Video-to-Purchase and the
placement funnels on the UTC day, while the Revenue card puts an order on the store's
day. The proxy now adds the store's current offset in minutes (from the WordPress
timezone setting) to the four routes that serve those figures, so the service counts
plays, adds and orders on the store's own day. The per-video read, the dashboard
metrics, the placement funnels and the video funnel each add it with one line; the
other routes are unchanged. A service that does not know the parameter ignores it.

* Keep commenter details out of the public engagement response

The engagement activities route returns each comment's author email so
the player can decide whether to show Delete and Edit. The site now works
out whether a comment belongs to the viewer from the WordPress session and
returns an `is_own` flag instead; the display name and avatar are unchanged.

Editing or deleting a comment now checks, against a fresh copy from GoDAM
Central, that the signed-in user wrote it, whatever id or email the browser
sends. The player reads `is_own` for its controls and shows the server's
message if a delete is refused.

Tests cover the route response for a logged-out visitor, the owner, another
user and a guest, the cache, and refused edits and deletes, plus the
comment controls in the player.

* chore(i18n): update godam.pot line references for class-analytics.php

The new offset helper shifts the source lines of the strings below it. No string changed.

* fix(analytics): keep the "never added to the cart" hint to rows with no adds

The in-video hint depended only on the not-addable-in-video flag, so a
grouped or external row that does have adds (older data, or a product whose
type changed after it sold) said it was never added to the cart beside a
non-zero count. The grouped and external sentences now show only when the
row has no adds; otherwise it gets the neutral in-video sentence.

* Look through all of a video's comments when checking who wrote one

The check that runs before a comment is edited or deleted asked Central for
its default window, the newest 20 comments, so a comment that had moved out
of that window since the page loaded was refused. It now reads the thread in
pages of 100 until it finds the comment or reaches the end. The public list
still uses Central's default window.

The test stand-in for wp_timezone() now reads the zone a test sets, so it
can share a run with another test file's stand-in for the same function.

* Answer with an allow-list of video fields for users without store access

The three routes that mix video and store data (dashboard-metrics, fetch and
layer-analytics) removed a list of store fields from their responses. That
fails open: a store field the analytics service adds later would reach Editors
until someone added it to the list.

They now keep only the fields listed as video data, so a new field stays hidden
from Editors and Authors until it is classified. The per-layer counter rows are
kept only for video actions (viewed, clicked, hovered, skipped, submitted,
voted), which also drops a row for an action added later. Shop managers and
administrators still get the whole response.

Tests: a field added to each route's response is hidden from Editors and
Authors and reaches a Shop manager, a counter row for an unclassified action is
dropped, and no allow-list may name a store field.

* Keep the site origin exact, and refuse analytics reads without one

- Origins from the rtgodam_site_origin filter are lowercased and rejected
  when they carry userinfo, so they match what browsers report.
- The ten analytics reads behind upload_files take the request's site_url
  only when it is on the same host and port as home_url() and the scheme is
  the only difference (a proxy that ends TLS). Anything else, and the public
  views route, keeps the site's own origin.
- When the site has no origin the reads return a site_origin_unavailable
  error before calling the service, which reads an empty site_url as "all
  sites on the account".
- The is_user_logged_in test stub answers from $GLOBALS['rtgodam_stub']['user']
  and reports a logged-out visitor by default, so it does not shadow other
  stubs that need a logged-in user.

* Pin the conversion counters as visible to Editors and Authors

The total_conversion figure on top-videos and the layer counters
conversion_rate, converting_sessions, unique_converting_sessions and
layer_converting_sessions stay visible on purpose. They count sessions that
acted on a video and carry no products, amounts or orders, and they are how an
Editor sees how the form and call-to-action layers they build perform.

A total that counts an add-to-cart once can include some. That is accepted.
This test-only change makes the decision explicit: a later edit to the video
allow-lists can no longer hide these fields by accident.

* Document what can_view_store_data() means when WooCommerce is off

The capability lives on the roles, not in WooCommerce's code. Where WooCommerce
was never active, or removed its data on uninstall, nobody holds it, so even an
Administrator gets video data only from the mixed routes and a 403 from the
store routes. Deactivating WooCommerce leaves the capability on the roles.

The same docblock still said the mixed routes strip the store fields; they
answer with the VIDEO_DATA_* allow-lists since the previous round, so the
wording now says that. Documentation only; the POT changes only in source line
numbers.

* Answer the ownership check from the cached comments, and handle a refused edit

The check that runs before a comment is edited or deleted now looks in the
comments the site already holds first. Who wrote a comment never changes, so
a comment found there is answered with no call to Central. Only a comment the
cache does not hold is looked for on Central, and that lookup stops after five
pages of 100, so an unknown comment ID costs at most the list read and five
pages. The player only shows the newest 20 comments, so a comment someone is
acting on has moved down by a few places, not by hundreds.

Saving an edit now handles a refusal the way deleting already does: the form
leaves the sending state, the server's message goes to the store's error
action, and nothing is left unhandled.

* Validate origins strictly and convert internationalised hosts

The origin check accepted http://:80, http://ex..com and http://host:abc,
and passed unicode hosts through. It now requires a hostname made of valid
labels, an IPv4 address or a bracketed IPv6 address, and a port from 1 to
65535. Like a browser's window.location.origin, the result is lowercase,
has an internationalised host as punycode (idn_to_ascii, when intl is
available) and has no default port. The origin built from home_url() goes
through the same check as the filter result.

* Make the test stand-ins for home_url() and the API base agree with the other stub file

tests/stubs/engagement-functions.php and tests/stubs/site-functions.php both
define home_url() and RTGODAM_API_BASE behind function_exists/defined guards,
so whichever file loads first wins. The home_url() here ignored the value a
test sets. It now reads the same key and default as the other file, and the
API base constant has the same value in both, so the load order no longer
changes any result. EngagementCommentPrivacyTest sets its own home_url
instead of relying on the default.

* Match counter rows on the action column only

The per-layer counter rows for an Editor or Author were kept when any
column held a video action, so a layer named "viewed" kept its
added_to_cart row. Check only the action column (index 1 of
layer_type_stats rows, index 3 of layer_details rows) and drop a row that
is too short to have one.

Also say in the docblock that the conversion counters include add-to-cart
sessions on WooCommerce layers and stay visible on purpose.

* Add the 2.3.1 changelog entries for the combined release

One line each for the user-visible changes in #2147 to #2153, next to the What's New fix, and the release date is now September 30, 2026.

* Ask for GoDAM for Woo 2.2.1 or newer

GoDAM 2.3.1 shows the "update the add-on" notice for GoDAM for Woo older than 2.2.1 (the minimum was 2.0.0). It is a notice only and switches nothing off.

* chore(i18n): regenerate godam.pot for qa/plugin-followups

Built from a clean assets/build (npm ci, npm run build:prod), then both make-pot passes with --slug=godam so the header matches develop's (only Project-Id-Version differs, 2.3.1 from #2146).

* Set the 2.3.1 release date to October 5, 2026 and replace @SInCE n.e.x.t with 2.3.1

- CHANGELOG.md and readme.txt: v2.3.1 is dated October 5, 2026. readme.txt keeps the newest three entries (2.3.1, 2.3.0, 2.2.4).
- inc/helpers/custom-functions.php: the four @SInCE n.e.x.t placeholders are now @SInCE 2.3.1. None are left in the tree.

* Clear the Plugin Check warnings: ship composer.json, mark the gallery block's local variable

Plugin Check flagged the vendor folder shipping without composer.json, and $inner_block_video_ids in the gallery-v2 render.php as a non-prefixed global. render.php runs inside WP_Block::render(), so the variable is local, and the shared gallery template reads it by this name.

* Gravity Forms recorder: stop counting one recording as two files (#2158)

* Gravity Forms recorder: stop counting one recording as two files

Gravity Forms 2.9.18+ treats the GoDAM Record field as a file upload: it saves
the recording to its temp folder on a page change or a failed submit and
counts that saved copy together with any file sent again. The recorder
restores the recording after every page load and put it back into the file
input, so the next submit failed with "Number of files (2) exceeds limit (1)",
one more per page on longer forms.

- Recorder: when Gravity Forms already saved the recording, show the restored
  preview but do not send the file again; removing the recording also drops
  the saved copy, so a new recording replaces it.
- Field: a recording sent in the current request replaces any saved copy, so
  the field always counts one file.

* Gravity Forms recorder: guard the submission-files override on Gravity Forms before 2.9.18

The override makes get_submission_files() exist on the GoDAM Record field even
where Gravity Forms has no such method, so a plugin that checks method_exists()
before calling it would hit the missing parent method. Return empty lists there.
Checks the parent class by name, since get_parent_class() without an argument is
deprecated in PHP 8.3.

* Set the 2.3.1 release date to October 6, 2026, add the Gravity Forms recorder fix to the changelog

Replaces the @SInCE n.e.x.t that #2158 brought in with 2.3.1 and regenerates
godam.pot for the line references #2158 moved.

---------

Co-authored-by: Vishal Kumar <vishnshiv3@gmail.com>
Copilot AI balanced review requested due to automatic review settings October 6, 2026 12:20

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot wasn't able to review any files in this pull request. Check if the Files changed in this pull request are included in default exclusions.

@github-actions

github-actions Bot commented Oct 6, 2026

Copy link
Copy Markdown

🔍 WordPress Plugin Check Report

⚠️ Status: Passed with warnings

📊 Report

🎯 Total Issues ❌ Errors ⚠️ Warnings
13 0 13

⚠️ Warnings (13)

📁 readme.txt (2 warnings)
📍 Line 🔖 Check 💬 Message
0 mismatched_plugin_name Plugin name "GoDAM - Organize WordPress Media Library & File Manager with Unlimited Folders for Images, Videos & more" is different from the name declared in plugin header "GoDAM".
0 trademarked_term The plugin name includes a restricted term. Your chosen plugin name - "GoDAM - Organize WordPress Media Library & File Manager with Unlimited Folders for Images, Videos & more" - contains the restricted term "wordpress" which cannot be used at all in your plugin name.
📁 assets/build/css/main.css (1 warning)
📍 Line 🔖 Check 💬 Message
0 EnqueuedStylesScope This style is being loaded in all contexts.
📁 assets/src/libs/analytics.min.js (5 warnings)
📍 Line 🔖 Check 💬 Message
0 EnqueuedScriptsScope This script is being loaded in all frontend contexts.
0 NonBlockingScripts.NoStrategy This script on http://localhost:8880 (with handle analytics-library) is loaded in the footer. Consider a defer or async script loading strategy instead.
0 NonBlockingScripts.NoStrategy This script on http://localhost:8880/2026/10/06/hello-world/ (with handle analytics-library) is loaded in the footer. Consider a defer or async script loading strategy instead.
0 NonBlockingScripts.NoStrategy This script on http://localhost:8880/sample-page/ (with handle analytics-library) is loaded in the footer. Consider a defer or async script loading strategy instead.
0 NonBlockingScripts.NoStrategy This script on http://localhost:8880/demo-attachment-post/ (with handle analytics-library) is loaded in the footer. Consider a defer or async script loading strategy instead.
📁 assets/build/js/main.min.js (5 warnings)
📍 Line 🔖 Check 💬 Message
0 EnqueuedScriptsScope This script is being loaded in all frontend contexts.
0 NonBlockingScripts.NoStrategy This script on http://localhost:8880 (with handle rtgodam-script) is loaded in the footer. Consider a defer or async script loading strategy instead.
0 NonBlockingScripts.NoStrategy This script on http://localhost:8880/2026/10/06/hello-world/ (with handle rtgodam-script) is loaded in the footer. Consider a defer or async script loading strategy instead.
0 NonBlockingScripts.NoStrategy This script on http://localhost:8880/sample-page/ (with handle rtgodam-script) is loaded in the footer. Consider a defer or async script loading strategy instead.
0 NonBlockingScripts.NoStrategy This script on http://localhost:8880/demo-attachment-post/ (with handle rtgodam-script) is loaded in the footer. Consider a defer or async script loading strategy instead.

🤖 Generated by WordPress Plugin Check Action • Learn more about Plugin Check

@subodhr258
subodhr258 requested a review from elifvish October 6, 2026 12:44

@elifvish elifvish left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Back-sync looks clean. No file changes (0 files in the diff), and the branch is a single merge commit (6ea4089) bringing the #2161 release merge onto develop. #2161 is merged into main as the PR says.

Security: nothing to flag, since no code changes.
Correctness: the tree is identical to develop; only the merge ancestry changes. develop is currently 1 commit ahead of this branch, so GitHub may need a refresh, but the PR is MERGEABLE.
Other: as the description says, please merge with a merge commit, not squash, or main will stay diverged from develop.
Consumers: none affected.

Verification

  • Checked with the GitHub compare API at head 6ea4089: 1 commit, 2 parents, 0 files changed. Confirmed #2161 merged (develop to main).
  • No test suites or Playwright runs, because nothing in the tree changes.
  • Not covered: the content of the release itself, which was reviewed in #2160 and #2161.

@subodhr258
subodhr258 merged commit 4d19569 into develop Oct 7, 2026
9 of 10 checks passed
@subodhr258
subodhr258 deleted the sync/main-to-develop-2.3.1 branch October 7, 2026 10:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants