Skip to content

Releases: escalated-dev/escalated-laravel

v1.5.1

Choose a tag to compare

@mpge mpge released this 04 Jun 05:29

Patch release on top of v1.5.0 (the newsletter system release).

Fixed

  • CI/build: ignore the laravel/framework 11.x security advisories that Composer 2.9+ excludes during dependency resolution, which was breaking the Laravel 11 compatibility test matrix. Scoped to this package's root config.audit.ignore — does not propagate to host apps. No runtime/code changes.

See v1.5.0 for the newsletter system, add_follower, ticket subjects, and permission enforcement.

v1.5.0 — Newsletter system

Choose a tag to compare

@mpge mpge released this 04 Jun 05:10

Added

  • Newsletter system — admin-only broadcast feature: campaigns, recipient lists (static + dynamic segments), templates, per-recipient deliveries with open/click tracking, one-click unsubscribe, view-in-browser, and ESP bounce/complaint webhooks (Postmark/Mailgun/SES/SendGrid). Driven by the escalated:newsletters:dispatch scheduled command (per-minute rate limiting, retry backoff, auto-pause on high bounce rates). Disabled by default behind escalated.enable_newsletters. (#103, #128)
  • add_follower workflow action — auto-subscribe a host user as a ticket follower from a workflow rule. (#127)
  • Ticket subjects — attach host-app entities a ticket is about (Project, Customer, asset…), distinct from the requester. (#89)
  • escalated.permissions is now shared with the Inertia frontend for per-permission nav gating. (#129)

Security

  • Newsletter admin routes now enforce newsletters.manage (all actions) and newsletters.send (send/test). Previously seeded but unchecked. (#129)

Full changelog: https://github.com/escalated-dev/escalated-laravel/blob/main/CHANGELOG.md

v1.4.1

Choose a tag to compare

@mpge mpge released this 29 May 11:53
a9ab060

Patch release fixing upgrade-safety regressions found in a breaking-change audit of v1.4.0.

Fixed

  • Skill management on upgraded installs. v1.4.0 added the routing_tag_ids/routing_department_ids skill columns by editing the original create_escalated_skills_table migration, which never re-runs on an existing install — so apps upgraded from an earlier version were missing the columns and every skill create/edit failed (Skill::saving() always writes them). Adds a Schema::hasColumn-guarded backfill migration (no-op on fresh installs).
  • AssignTicketRequest (Admin/Agent assign endpoints) again validates that agent_id exists, so unknown/garbage input returns a clean 422 instead of a 500. UUID/string keys are still accepted.
  • AssignmentService::__construct() $skillRoutingService is now optional (container fallback), restoring the v1.3.0 new AssignmentService($manager) single-argument signature.

Upgrade notes (from < 1.4.0)

  • The int → int|string widening for UUID/string user-key support touches the public TicketDriver::assignTicket() contract and Ticket methods (assign, follow, unfollow, isFollowedBy, scopeAssignedTo). If you implement TicketDriver or subclass Ticket with int-typed parameters, widen them to int|string to avoid a PHP "must be compatible" fatal. Callers are unaffected.
  • TicketAssigned::$agentId is now int|string (a string for UUID/string-keyed apps). Integer-keyed apps are unchanged.

Full changelog: v1.4.0...v1.4.1

v1.4.0

Choose a tag to compare

@mpge mpge released this 29 May 02:46

Highlights

UUID / string host user keys — Escalated for Laravel now works with host apps whose user primary key is a UUID, ULID, or string, not just an integer. SavedView::scopeForUser() and every other user-id parameter accept int|string (fixing a TypeError 500 on /support/admin/tickets for non-integer-keyed apps), user ids are never cast to int (which corrupted UUIDs), and the package's user-referencing migration columns are auto-typed to match your user_model via the new escalated.user_key_type config (auto by default). Integer-keyed apps are unaffected.

Custom Ticket Actions — register host-defined agent ticket buttons that dispatch a TicketCustomActionTriggered event (with an audit note) when clicked.

Added

  • Custom Ticket Actions via events (#107, #108)
  • Auto-detect host user key type for migration columns + escalated.user_key_type (#112)
  • Skills-based ticket routing (#95)
  • Mobile customer & guest support API (#104)
  • Expanded SSO provider configuration (#96)
  • Shared escalated-dev/locale translation consumption

Fixed

  • UUID/string host user keys throughout (#110)
  • Restrict skill assignment to role-bearing users; transactional skill store/update (#100)
  • Show 2FA recovery codes after confirmation (#97)

Security

Full changelog: v1.3.0...v1.4.0

v1.3.0 - Admin users-management page

Choose a tag to compare

@mpge mpge released this 10 May 21:51
1b2c5b3

Added

  • Admin → Users page (#94): new Admin/UserController exposes the host User table (paged, searchable) plus a PATCH /admin/users/{user}/role endpoint that flips one role at a time. Companion Vue page lands in escalated@v0.8.0. Surfaced from escalated#60, where the reporter asked "how do you create agent?" — admins no longer have to drop into tinker to grant or revoke staff access.

    Demoting yourself from admin is rejected server-side so an admin cannot lock themselves out of the panel they're using. Demoting an admin via the agent toggle revokes both flags in one step.

Notes

The default install pins this to the is_admin / is_agent columns the install command tells hosts to add. Hosts wiring the gates differently (Spatie roles, custom pivots) should override Admin/UserController in their own routes — there's a comment to that effect in the class.

Upgrade

composer update escalated-dev/escalated-laravel

No new migrations. The Users nav entry shows up automatically once you upgrade @escalated-dev/escalated to 0.8.0.

Full Changelog: v1.2.5...v1.3.0

v1.2.5

Choose a tag to compare

@mpge mpge released this 07 May 14:22
0389bc5

Highlights

Resolves the install failure reported in #88 for real this time (#92)

The actual underlying issue (surfaced thanks to verbose migrate output): MariaDB and older MySQL versions reject escalated_macros's foreign key to users(id) with errno: 150 "Foreign key constraint is incorrectly formed" whenever the host app's users table doesn't exactly match what we expect. Reproduced on MariaDB 11 under four common host configurations:

  • users table on MyISAM (legacy Laragon / WAMP default_storage_engine=MyISAM)
  • users.id is signed BIGINT (project upgraded from Laravel pre-5.8)
  • users.id is INT UNSIGNED (project from pre-5.8 increments())
  • users.id is CHAR(36) UUID (custom user model with HasUuids)

We can't control any of those four from a package, so this release drops the foreign-key constraints from the five migrations that pointed at the host's users table:

  • escalated_macros.created_by
  • escalated_ticket_followers.user_id
  • escalated_saved_views.user_id
  • escalated_mentions.user_id
  • escalated_tickets.snoozed_by

Each becomes a plain unsignedBigInteger column. Foreign keys to our own tables (e.g. escalated_tickets) stay — those always work because we own both sides.

This is the same pattern spatie/laravel-permission, spatie/laravel-activitylog, and Filament use for the same reason.

Better diagnostics on install (#91)

escalated:install was using callSilently('migrate') wrapped in a task component, which swallowed Laravel's actual SQL error and just printed FAIL. Now it prints the full migrate output (per-migration timing, failing migration name, full PDO/SQL error). Same treatment for db:seed. Took a back-and-forth with the reporter to get the real error before — won't happen again.

Backwards compatibility

Forward-compatible only. Anyone with a successful prior install keeps their existing FK constraints because Laravel doesn't re-run migrations once recorded. Anyone hit by #88 sees migrations 14+ run cleanly on their next php artisan migrate.

If a host app hard-deletes users, orphaned created_by / user_id rows in our tables won't be cascade-cleaned anymore (most apps soft-delete users so this is a non-issue in practice). If you need cleanup, attach a User-deleting model observer in the host app.

Upgrade

composer update escalated-dev/escalated-laravel
php artisan escalated:install

Full Changelog: v1.2.4...v1.2.5

v1.2.4

Choose a tag to compare

@mpge mpge released this 07 May 03:42
1791741

Highlights

Install translations now actually flow from escalated-dev/locale (#90)

The central escalated-dev/locale package was already declared as a composer dep but wasn't actually serving any translations — its JSON uses camelCase keys (matching the Vue frontend) while every PHP-side __() call used snake_case, so the central package was effectively dead weight and translations came entirely from the package's own resources/lang/ PHP fallback.

This release wires the install command to actually pull from central:

  • New JSON-to-Translator bridge in EscalatedServiceProvider (bridgeCentralGroups()) reads each <locale>.json from escalated-dev/locale and injects the nested data into Laravel's translator under the escalated namespace via addLines(). After the bridge fires, __('escalated::commands.install.publishingConfig') resolves against the central JSON. The PHP fallback's commands.php is no longer loaded for the commands group in production — central is the single source of truth.
  • The bridge auto-converts central's {placeholder} syntax (matches the Vue t() helper) to Laravel's :placeholder convention on the way in, so call sites stay idiomatic.
  • All escalated::commands.install.* keys converted from snake_case to camelCase in InstallCommand.php and in every locale's commands.php fallback. The fallback now serves as a parity-with-central offline backup for the dev path.
  • Composer constraint bumped to escalated-dev/locale: ^0.1.8 so the new install keys (seedingPermissions, runningMigrations, runMigrationsConfirm, stepSeed from escalated-locale v0.1.8) are guaranteed present.

Scope (and what's still local)

Only the commands group is bridged today. Other groups — enums, notifications, emails, messages, validation — continue to load from resources/lang/ because their snake_case enum-value keys (e.g. enums.status.in_progress) don't yet have camelCase parity in central. Migrating those is a separate follow-up since enum values are persisted in user databases.

Backwards compatibility

  • Composer install with v0.1.8 central: install command translations resolve via central (production path).
  • Dev/offline scenarios without composer: bridge resolver returns null, install command falls back to the package's resources/lang/ PHP files (now camelCase, matching the call sites).
  • Host-app overrides at lang/vendor/escalated/{locale}/commands.php still work via Laravel's loadNamespaceOverrides mechanism (unchanged).
  • No public API changed — internal i18n plumbing only.

Test plan

  • New tests/Unit/CentralLocaleBridgeTest.php (3 tests, 23 assertions) locks in the bridge: populates $loaded['escalated']['commands'], resolved values match central JSON exactly across en and fr, and {placeholder}:placeholder rewriting works.
  • Full suite: 609 passed.

Full Changelog: v1.2.3...v1.2.4

v1.2.3

Choose a tag to compare

@mpge mpge released this 07 May 01:48
94e5c44

Highlights

escalated:install no longer breaks on a clean install (#89, resolves #88)

Previously the command published migration files and then immediately tried to seed escalated_permissions — a table that hadn't been created yet, causing every fresh install to fail with SQLSTATE[42S02] Table doesn't exist. The command now prompts to run migrations + seed default permissions in the correct order, and the printed setup instructions are state-aware (they drop the migrate / seed steps when the user opted into auto-running them).

Also adds the missing seeding_permissions translation key across all 14 locales, fixes a __() call that was silently passing a fallback string into the $locale slot, and adds new running_migrations, run_migrations_confirm, step_seed keys for the new prompts.

Migration / index fixes

  • fix(migration): assign explicit name to delayed_actions index (#87) — pre-empts the Identifier name too long MySQL error for users with a custom escalated.table_prefix longer than 13 characters.
  • fix(migration): assign explicit name to ticket_links unique index (#86, thanks @matalaweb) — fixes the same class of bug for the default escalated_ prefix on MySQL/PostgreSQL.

Other changes since v1.2.2

  • feat(i18n): consume central translations from @escalated-dev/locale (#85)
  • chore(deps): modernize host-app frontend (Vite 8 + Tailwind 4) (#84)
  • Various dependabot updates

Backwards compatibility

All changes in this release are backwards-compatible. The migration index renames in #86 / #87 only take effect on fresh installs — anyone who already ran the original migrations keeps the old auto-generated index names (Laravel doesn't re-run migrations).

Full Changelog: v1.2.2...v1.2.3

v1.2.2

Choose a tag to compare

@mpge mpge released this 20 Apr 15:23
3e44949

Fixed

  • Customer priority filter now actually filters (#66, fixes #64) — Customer\TicketController@index was dropping the priority URL param (and several other visible filters) before calling the driver. The shared TicketFilters.vue component still rendered a priority dropdown, so the param landed in the URL but never reached a where clause. Allow-list now matches every key the UI sends: status, priority, ticket_type, search, tag, has_attachments, created_after, created_before, plus sort controls. Agent/admin/api paths were never affected — regression tests added for all three to prevent drift.

  • Graceful fallback when the host's users table has no name column (#65, fixes #63) — ticket search (Ticket::scopeSearch + LocalDriver requester filter) assumed a name column existed on the host's user model and would either error against Postgres (column users.name does not exist) or silently return empty against sqlite. The new Escalated::applyUserSearch() helper now consults config('escalated.user_display_column'), checks Schema::hasColumn once per process, and silently degrades to email-only search when the column doesn't exist. Hosts with split first_name/last_name columns now work out of the box; setting ESCALATED_USER_DISPLAY_COLUMN=first_name gives them full first_name+email search.

  • Install command no longer duplicates migrations (#62, fixes #61) — re-running php artisan escalated:install (e.g. during package upgrades) previously republished every migration with a fresh timestamp prefix, leaving the host with duplicate *_create_escalated_*_table.php files and a broken migrate run. Install now globs for existing *_create_escalated_*_table.php files and skips publication if any are present; --force deletes the stale copies first before republishing.

Full changelog: v1.2.1...v1.2.2

v1.2.1

Choose a tag to compare

@mpge mpge released this 19 Apr 00:17

Fixed

  • Postgres compatibility for the Reports endpoint (#60, fixes #59) — ReportingService and ReportController::avgFirstResponseHours previously emitted MySQL-only TIMESTAMPDIFF(HOUR, …) and DATE_FORMAT(…) SQL, which Postgres rejects with column "hour" does not exist. Helpers now match on the driver across sqlite | pgsql | mysql and emit EXTRACT(EPOCH FROM (to - from)) / 3600 / to_char(…, 'YYYY-MM') etc. on Postgres. 16 new unit tests assert exact SQL per driver.

Internal

  • New Docker dev/demo environment under docker/ (#58). docker compose up --build from docker/ boots a 3-container Postgres-backed Laravel host (PHP 8.3 + Postgres 16 + Mailpit) with the package installed and a /demo click-to-login picker. Excluded from the Composer dist via archive.exclude — won't appear in vendor/.
  • Added Escalated\Laravel\Database\Factories\ to the production PSR-4 autoload so Model::factory() resolves at runtime in real installs (same class of bug as #55).

Full changelog: v1.2.0...v1.2.1