Skip to content

Releases: modx-pro/Sendex

Release list

v2.0.1

Choose a tag to compare

@github-actions github-actions released this 30 Jul 14:06
45aa4c6

[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 documents sendex_unsubscribe_page (fallback: site_start).
  • [#119] Mgr row-action icon buttons (edit/disable/send/remove) did nothing when the click hit the inner <i>: shared SelectionMixin.onClick now finds the button via getTarget('button'), resolves the row via findRowIndex, and reads the action from data-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 getlist no longer uses JOIN/subquery SQL (subscriber count and template name are added in prepareRow); grid refresh after save is deferred so the create window can close first; getlist/get accept view_sendex as well as view_document; create beforeSet() returns strict true for MODX 3. Image column renderer uses Sendex.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 #105 migration, avoiding Unknown column sxQueue.claimed_at on live upgrades.

2.0.0-pl

Choose a tag to compare

@Ibochkarev Ibochkarev released this 25 Jul 06:14

[2.0.0-pl] - 2026-07-25

Security

  • [#51] Subscriber export goes through the authenticated manager connector (no public CSV under assets/); processor requires edit_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 src and 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; 0 disables).

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 via newsletter_id and optional widgetKey.
  • [#38] Guest subscribe can skip email confirmation: snippet &confirmEmail=0 or system setting sendex_confirm_email; domain helper subscribeGuest() keeps one subscribe path.
  • [#29] Mgr newsletter «Send to subscribers»: one action runs addQueues + sxQueueSender::flush for 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 (only OnUserActivate / OnUserSave), so a cancelled activation cannot attach guests to an inactive user.
  • [#40] MODX 3 package install: registerNamespace sets assets_path; build script aliases modPackageBuilder, skips model regen on MODX 3 (preserves global sx* maps); mgr menu without modAction; Phinx migration property no longer conflicts with AbstractMigration::$tables on PHP 8.4.
  • [#74] MODX 3 mgr: SendexIndexManagerController aliases menu action index (no duplicate menu remap).
  • [#74] MODX 3 bootstrap: bootstrap.php for connector/mgr/cron; processor autoload + modProcessor aliases; sxModxCompat (mail/parser/registry) and sxUserProfile (user/profile placeholders); ExtJS mgr icon/menu polish on MODX 3.
  • [#42] Multi-widget AJAX: authenticated subscribe no longer breaks request scoping ($id shadowing); confirm link keeps newsletter_id; unsubscribe widget keeps widget_key; email links without sendex_widget_key route to the default snippet instance (empty widgetKey).
  • [#60] Mgr grids: empty checkbox selection no longer sends ids:''; alert via Sendex.utils.requireSelectedIds, row action falls back to menu.record.
  • [#59] Deleting a newsletter removes its sxQueue rows via xPDO composite Queues; upgrade migration purges queue rows left orphaned by earlier deletes.
  • [#57] addQueues returns an error when 0 queue rows were created (all subscribers skipped); mgr queue/add reports 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 = ? with rowCount() fallback-safe behavior for single-owner delivery.
  • [#105] Queue claim switches to UPDATE ... SET claimed_at, attempts = attempts + 1 ... WHERE claimed_at IS NULL with legacy delete fallback.
  • [#56] Unsubscribe from email: snippet resolves newsletter by subscriber code when snippet &id differs; default letter link includes newsletter_id (not MODX resource id).
  • [#67] / [#52] Schema and upgrade: Sendex tables use InnoDB; queue index renamed user_id → subscriber_id; addQueues stores sxSubscriber.id (not user_id); Phinx backfill remaps legacy queue rows (by user_id, guests by email).
  • [#54] isSubscribed matches by user_id OR email within a newsletter; unique key is (newsletter_id, email); guest rows attach user_id when the same email confirms as a user.
  • [#39] Guest rows merge onto modUser on OnUserActivate / 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] confirmEmail restores registry hash when subscribe() fails so the confirm link stays usable; remaining TTL kept via _expires; checkEmail shares sxSubscribeRegistry::store.
  • [#61] sxSubscriber::save keeps existing code (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: cast group_id and newsletter_id to int; require user Profile (innerJoin).
  • confirmEmail returns the subscribe() result when confirming a hash for another newsletter.
  • Mgr newsletter/update no longer clears active when the field is omitted from the request (partial saves, QA/API).
  • PHP 7.4–8.4: dynamic properties, null Profile in addQueues, PHPMailer ErrorInfo.
  • [#28] sxProcessorInput::parseIds accepts ids as array or CSV; queue send from snippet/code matches ExtJS runProcessor input.

Changed

  • [#68] ExtJS mgr grids share Sendex.grid.SelectionMixin (getSelectedIds, confirmWithSelection, ajaxWithSelection) instead of per-grid _getSelectedIds copy-paste.
  • [#73] Newsletter mgr getlist: COUNT runs on filters only; JOIN/COUNT(Subscribers)/GROUP BY moved to prepareQueryAfterCount via sxNewsletterListQuery.
  • [#103] Group subscribe loads only relevant existing rows (user_id/email subset) instead of all newsletter subscribers.
  • [#103] Subscriber schema adds user_id index via Phinx migration 20260725170000_subscriber_user_id_index.php.
  • [#105] Queue schema adds claim/retry fields (claimed_at, attempts, expires_at) and normalizes subscriber_id to NOT NULL DEFAULT 0.
  • [#105] Sendex tables are converted to utf8mb4_unicode_ci; subscriber email is backfilled to lowercase and uses case-insensitive utf8mb4 collation.
  • [#103] Mgr controller ACL now checks view_sendex or view_document; transport menu declares explicit view_document permission.
  • [#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 sxSubscriber lookup (findSubscriber) instead of SELECT in isSubscribed plus a second SELECT in attachUserToSubscriber.
  • [#70] add_group bulk-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] addQueues batch-loads modUser + Profile (id:IN) once instead of N+1 per subscriber.
  • [#66] sxNewsletterMailer centralizes From/Reply-To/HTML setup for activation mail and queue delivery; activation reply-to uses email_reply like queue build.
  • [#69] Mgr processors share sxSendexProcessor (edit_document + requireIds / parseIds); get-list processors declare view_document.
  • [#62] Split sxNewsletter into sxNewsletterSubscription, sxNewsletterQueueBuilder, sxSendexEvent; model keeps a thin public API for processors/snippet.
  • Breaking (DB): existing sendex_* tables are converted to InnoDB on upgrade; queue subscriber_id meaning is sxSubscriber.id after backfill (join Queue→Subscriber for guests works). Soft FK to core modUser tables are not added.
  • add_group processor requires edit_document permission.
  • 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.lock in git (removed from .gitignore) for reproducible CI installs.
  • Package schema install/upgrade runs via Phinx instead of ad-hoc resolve.tables Manager calls.