Releases: modx-pro/Sendex
Releases · modx-pro/Sendex
Release list
v2.0.1
[2.0.1-pl] - 2026-07-30
Fixed
- Email templates: nested
[[~[[++site_start]]]]unsubscribe links became[[~[[57]]]]and logged "Bad link tag". Queue body render now provides[[+unsubscribe_url]], flattens residual nested[[~[[N]]]]before parse, and documentssendex_unsubscribe_page(fallback:site_start). - [#119] Mgr row-action icon buttons (edit/disable/send/remove) did nothing when the click hit the inner
<i>: sharedSelectionMixin.onClicknow finds the button viagetTarget('button'), resolves the row viafindRowIndex, and reads the action fromdata-action. - [#114] Mgr newsletter create appeared to hang on «Загружается…» and the grid stayed empty after reload (row was saved; duplicate-name error on retry). Newsletter
getlistno longer uses JOIN/subquery SQL (subscriber count and template name are added inprepareRow); grid refresh after save is deferred so the create window can close first;getlist/getacceptview_sendexas well asview_document; createbeforeSet()returns stricttruefor MODX 3. Image column renderer usesSendex.utils.escapeHtmlAttr(ExtJS does not keep grid scope for column renderers). - [#111] Mgr row-action and menu icons no longer force
font-family: "Font Awesome 5 Free"(Sendex does not load FA5); icons inherit the mgr icon font on MODX 2.3+/3.x or bundled FA4 on older MODX. - [#25666] Transport package built on MODX 3.x stored vehicle class as
xPDO\Transport\xPDOObjectVehicle, which MODX 2.8.8/2.8.9 cannot load (install fails with ~30 "Could not load class" errors). Release build now runs on MODX 2.x so the manifest uses the legacy vehicle format that installs on both MODX 2.8+ and 3.x. - Queue claim columns (
claimed_at,attempts,expires_at) are ensured on Sendex bootstrap when Phinx did not apply#105migration, avoidingUnknown column sxQueue.claimed_aton live upgrades.
2.0.0-pl
[2.0.0-pl] - 2026-07-25
Security
- [#51] Subscriber export goes through the authenticated manager connector (no public CSV under
assets/); processor requiresedit_document. - CSV cells that look like spreadsheet formulas are prefixed so Excel/LibreOffice do not execute them.
- [#103] Mail header fields (
email_from,email_reply,email_to) are sanitized to strip CR/LF/control characters before PHPMailer configuration. - [#103] Mgr newsletters grid escapes image
srcand fixes broken boolean renderer span tags to prevent stored XSS via crafted image values. - [#103] Frontend subscribe/unsubscribe adds optional CSRF token validation (
sendex_csrf_protect). - [#103] Guest confirm flow adds per-email rate limiting (
sendex_confirm_rate_limit, seconds;0disables).
Added
- [#42] Frontend AJAX subscribe/unsubscribe: JSON
{success, message, html}from snippet, default chunks +assets/components/sendex/js/web/sendex.js. Multi-widget pages scope POST vianewsletter_idand optionalwidgetKey. - [#38] Guest subscribe can skip email confirmation: snippet
&confirmEmail=0or system settingsendex_confirm_email; domain helpersubscribeGuest()keeps one subscribe path. - [#29] Mgr newsletter «Send to subscribers»: one action runs
addQueues+sxQueueSender::flushfor the newsletter (mgr/newsletter/send); button in grid row actions and update window. - [#46] Search subscribers and queue by email or username.
- [#44] Plugin events for subscribe/unsubscribe (
sxOnBeforeSubscribe,sxOnSubscribe,sxOnBeforeUnsubscribe,sxOnUnsubscribe). - PHPUnit coverage for subscribe/unsubscribe events (stubs, no MODX install).
- [#72] Focused PHPUnit coverage for queue claim (#55), plus regression contracts for #52 / #58 / #61.
- Phinx migrations:
core/components/sendex/phinx.php,migrations/, install/upgrade resolver; metadata table{prefix}sendex_migrations. - [#104] Queue lifecycle plugin events:
sxOnBeforeAddQueues,sxOnAddQueues,sxOnBeforeQueueSend,sxOnQueueSend,sxOnQueueSendFailed,sxOnQueueFlushComplete. - [#104] Subscribe/unsubscribe events pass
source(snippet|ajax|confirm|mgr|guest).
Fixed
- [#110] Mgr queue newsletter combo keeps the selected newsletter after click; MODX 3 tab/grid spacing aligned; manager menu uses envelope icon on MODX 3.
- [#104] Guest merge no longer runs on
OnBeforeUserActivate(onlyOnUserActivate/OnUserSave), so a cancelled activation cannot attach guests to an inactive user. - [#40] MODX 3 package install:
registerNamespacesetsassets_path; build script aliasesmodPackageBuilder, skips model regen on MODX 3 (preserves globalsx*maps); mgr menu withoutmodAction; Phinx migration property no longer conflicts withAbstractMigration::$tableson PHP 8.4. - [#74] MODX 3 mgr:
SendexIndexManagerControlleraliases menu actionindex(no duplicate menu remap). - [#74] MODX 3 bootstrap:
bootstrap.phpfor connector/mgr/cron; processor autoload +modProcessoraliases;sxModxCompat(mail/parser/registry) andsxUserProfile(user/profile placeholders); ExtJS mgr icon/menu polish on MODX 3. - [#42] Multi-widget AJAX: authenticated subscribe no longer breaks request scoping (
$idshadowing); confirm link keepsnewsletter_id; unsubscribe widget keepswidget_key; email links withoutsendex_widget_keyroute to the default snippet instance (emptywidgetKey). - [#60] Mgr grids: empty checkbox selection no longer sends
ids:''; alert viaSendex.utils.requireSelectedIds, row action falls back tomenu.record. - [#59] Deleting a newsletter removes its
sxQueuerows via xPDO compositeQueues; upgrade migration purges queue rows left orphaned by earlier deletes. - [#57]
addQueuesreturns an error when 0 queue rows were created (all subscribers skipped); mgrqueue/addreports the created count on success. - [#55] Queue send claims the row (remove-before-send) so parallel cron/mgr workers do not double-deliver; mail failure requeues and logs; cron logs non-true
send()results. - [#103] Queue claim uses atomic
DELETE ... WHERE id = ?withrowCount()fallback-safe behavior for single-owner delivery. - [#105] Queue claim switches to
UPDATE ... SET claimed_at, attempts = attempts + 1 ... WHERE claimed_at IS NULLwith legacy delete fallback. - [#56] Unsubscribe from email: snippet resolves newsletter by subscriber
codewhen snippet&iddiffers; default letter link includesnewsletter_id(not MODX resourceid). - [#67] / [#52] Schema and upgrade: Sendex tables use InnoDB; queue index renamed
user_id→subscriber_id;addQueuesstoressxSubscriber.id(notuser_id); Phinx backfill remaps legacy queue rows (by user_id, guests by email). - [#54]
isSubscribedmatches byuser_idORemailwithin a newsletter; unique key is(newsletter_id, email); guest rows attachuser_idwhen the same email confirms as a user. - [#39] Guest rows merge onto
modUseronOnUserActivate/OnUserSave(sxSubscriberMerge); registration does not create a second subscriber row for the same email. - [#103] Guest merge query filters
user_id=0+ email at SQL level (no full scan in PHP). - [#58]
confirmEmailrestores registry hash whensubscribe()fails so the confirm link stays usable; remaining TTL kept via_expires;checkEmailsharessxSubscribeRegistry::store. - [#61]
sxSubscriber::savekeeps existingcode(generate only when empty) so unsubscribe links stay valid. - [#53] Snippet: authenticated user without Profile no longer triggers TypeError on PHP 8; merge via
sxUserPlaceholders. - [#47] Adding a user group to a newsletter subscribes only active and unblocked users.
add_group: castgroup_idandnewsletter_idto int; require user Profile (innerJoin).confirmEmailreturns thesubscribe()result when confirming a hash for another newsletter.- Mgr
newsletter/updateno longer clearsactivewhen the field is omitted from the request (partial saves, QA/API). - PHP 7.4–8.4: dynamic properties, null Profile in
addQueues, PHPMailerErrorInfo. - [#28]
sxProcessorInput::parseIdsacceptsidsas array or CSV; queue send from snippet/code matches ExtJSrunProcessorinput.
Changed
- [#68] ExtJS mgr grids share
Sendex.grid.SelectionMixin(getSelectedIds,confirmWithSelection,ajaxWithSelection) instead of per-grid_getSelectedIdscopy-paste. - [#73] Newsletter mgr
getlist: COUNT runs on filters only; JOIN/COUNT(Subscribers)/GROUP BY moved toprepareQueryAfterCountviasxNewsletterListQuery. - [#103] Group subscribe loads only relevant existing rows (
user_id/emailsubset) instead of all newsletter subscribers. - [#103] Subscriber schema adds
user_idindex via Phinx migration20260725170000_subscriber_user_id_index.php. - [#105] Queue schema adds claim/retry fields (
claimed_at,attempts,expires_at) and normalizessubscriber_idtoNOT NULL DEFAULT 0. - [#105] Sendex tables are converted to
utf8mb4_unicode_ci; subscriber email is backfilled to lowercase and uses case-insensitiveutf8mb4collation. - [#103] Mgr controller ACL now checks
view_sendexorview_document; transport menu declares explicitview_documentpermission. - [#103] Frontend i18n cleanup: lexicon-based request failure text, guest/anonymous labels, and email placeholder.
- [#103] CI adds integration smoke matrix (MODX 2.8/3.x, allow-failure), Clover coverage artifact, and tag-driven release workflow.
- [#71] Confirm/subscribe reuses one
sxSubscriberlookup (findSubscriber) instead of SELECT inisSubscribedplus a second SELECT inattachUserToSubscriber. - [#70]
add_groupbulk-subscribes group members via one query + chunked multi-row INSERT; mgr sync path skips per-row subscribe events (#44). - [#64] New queue rows store empty
email_body(compact mode); body is rendered at send from newsletter template; legacy rows with stored HTML still send as-is. - [#63]
addQueuesbatch-loadsmodUser+ Profile (id:IN) once instead of N+1 per subscriber. - [#66]
sxNewsletterMailercentralizes From/Reply-To/HTML setup for activation mail and queue delivery; activation reply-to usesemail_replylike queue build. - [#69] Mgr processors share
sxSendexProcessor(edit_document+requireIds/parseIds); get-list processors declareview_document. - [#62] Split
sxNewsletterintosxNewsletterSubscription,sxNewsletterQueueBuilder,sxSendexEvent; model keeps a thin public API for processors/snippet. - Breaking (DB): existing
sendex_*tables are converted to InnoDB on upgrade; queuesubscriber_idmeaning issxSubscriber.idafter backfill (join Queue→Subscriber for guests works). Soft FK to coremodUsertables are not added. add_groupprocessor requiresedit_documentpermission.- Newsletter create processor class renamed to
sxNewsletterCreateProcessor. subscribe()method name normalized to camelCase (PHP method names are case-insensitive;Subscribe()calls still work).- Declared PHP 7.4–8.4 support; CI lint matrix.
- Track
composer.lockin git (removed from.gitignore) for reproducible CI installs. - Package schema install/upgrade runs via Phinx instead of ad-hoc
resolve.tablesManager calls.