Announcing Mautic 7.2.0 Lynx Edition
What's Changed
🔧 Refactoring
🧑💻 Automated tests
- Automate test class reviews with Rector by @escopecz in #16220
- Clear the PHPStan baseline for all test files by @escopecz in #16295
- Modernize tests to PHP 8.0 syntax and readonly properties by @TomasVotruba in #16388
- Replace php.ini-dependent assert() calls with real PHPUnit assertions by @TomasVotruba in #16390
- Extract a single Reflection helper for setting private and protected values in tests by @TomasVotruba in #16392
- Call once() on the test case instead of statically by @TomasVotruba in #16395
- Stop relying on MockBuilder members that PHPUnit 11 makes non-public by @TomasVotruba in #16396
- Drop parent constructors in test cases and restore error handlers where needed by @TomasVotruba in #16398
- Make Twig test data providers static for PHPUnit 11 by @TomasVotruba in #16399
- Use assertSame() and assertCount() for more precise test failures by @TomasVotruba in #16401
- Stop calling any() as a public method ahead of PHPUnit 11 by @TomasVotruba in #16397
- Apply the first 20 type rules to the test suite by @TomasVotruba in #16391
- Inline setUp properties that are used only once by @TomasVotruba in #16400
- Prefer stubs over mocks to prepare for PHPUnit 12 by @TomasVotruba in #16406
- Remove dead code and add closure return types in tests by @TomasVotruba in #16389
- Add closure and property types to tests and standardize spacing by @TomasVotruba in #16411
- Apply the remaining type rules to the test suite by @TomasVotruba in #16414
- Make expects() and assertNull() calls explicit for PHPUnit 12 by @TomasVotruba in #16412
- Inline properties that only hold a stub by @TomasVotruba in #16427
- Mark test classes final to improve type coverage by @TomasVotruba in #16428
- Add strict types so tests verify exact scalar types by @TomasVotruba in #16430
- Type getExecutionDate() against its interface in tests by @TomasVotruba in #16451
- Add types to string-named method fetches in tests by @TomasVotruba in #16472
- Guard nullable values with instanceof to avoid calls on null in tests by @TomasVotruba in #16495
- Move mocks to stubs where a stub is enough by @TomasVotruba in #16528
- Remove any() expectations that assert nothing by @TomasVotruba in #16525
- Add instanceof checks to avoid calls on null in tests by @TomasVotruba in #16524
- Pass real strings to string assertions by @TomasVotruba in #16527
- Use exact assertions instead of closures returning bool by @TomasVotruba in #16526
- Add explicit returns to test callbacks by @TomasVotruba in #16530
- Mark abstract test classes abstract and enforce it with PHPStan by @TomasVotruba in #16529
- Use more precise assertion methods by @TomasVotruba in #16535
- Improve assertions and callback signatures in tests by @TomasVotruba in #16547
- Yield one use case per line in test data providers by @TomasVotruba in #16552
- Unify assertions on $this->assert*() by @TomasVotruba in #16496
- Call assertions on the test case instead of the Assert class by @TomasVotruba in #16569
- Enforce the no-table-prefix test convention with a PHPStan rule by @TomasVotruba in #16578
- Assign repositories directly in tests instead of using the service locator by @TomasVotruba in #16644
- Mark test support classes final by @TomasVotruba in #16734
- Apply the PHPUnit assert and mock Rector sets by @TomasVotruba in #16736
- Mark utils test fixtures final by @TomasVotruba in #16733
- Build RequestStack through its constructor in tests by @TomasVotruba in #16858
- Mark childless test support classes final by @TomasVotruba in #16886
- Upgrade the test suite to PHPUnit 11 by @TomasVotruba in #16891
- Remove unnecessary re-casting in tests by @TomasVotruba in #16905
⬆️ Dependencies
🧑💻 Developer experience
- Let plugins replace tokens in URLs through UrlTokenReplaceEvent by @fedys in #16199
- Clear parameter.phpDocType errors from the PHPStan baseline by @escopecz in #16294
- Correct PHPDoc return types on entities by @patrykgruszka in #16291
- Clear method.nonObject errors from the PHPStan baseline by @escopecz in #16292
- Clear empty.variable errors from the PHPStan baseline by @escopecz in #16293
- Inline the logger dependency instead of keeping an extra variable by @TomasVotruba in #16305
- Remove the always-true $isValid variable from FormController by @TomasVotruba in #16306
- Remove the always-true $cleanSlate variable by @TomasVotruba in #16307
- Clear throws.notThrowable errors from the PHPStan baseline by @patrykgruszka in #16297
- Clear the PHPStan baseline for TriggerApiController by @JonasLudwig1998 in #16296
- Clear property.defaultValue and property.phpDocType errors from the PHPStan baseline by @patrykgruszka in #16300
- Use prepared statements more consistently by @rohitpavaskar in #16138
- Fix PHPStan errors around the preSaveEntity API controller action by @JonasLudwig1998 in #16298
- Remove Gitpod support now that the service is deprecated by @escopecz in #16310
- Add void return types to closures that return nothing by @TomasVotruba in #16319
- Add known return types to arrow functions by @TomasVotruba in #16320
- Remove unnecessary if statements flagged by static analysis by @escopecz in #16299
- Add string and int types inferred from scalar values by @TomasVotruba in #16323
- Use short assignment operators by @TomasVotruba in #16328
- Add return types to helper classes by @TomasVotruba in #16332
- Add return types to entity classes by @TomasVotruba in #16335
- Add return types to model classes by @TomasVotruba in #16336
- Add return types to event classes by @TomasVotruba in #16331
- Raise the Rector code-quality rule level by @TomasVotruba in #16338
- Check objects with instanceof instead of empty() by @TomasVotruba in #16343
- Add the remaining return types inferred from typed properties by @TomasVotruba in #16341
- Add a blank line between class elements by @TomasVotruba in #16344
- Add parameter types inferred from property types by @TomasVotruba in #16347
- Add types for magic class methods by @TomasVotruba in #16345
- Return booleans directly instead of using if/else by @TomasVotruba in #16342
- Add parameter types inferred from parent calls by @TomasVotruba in #16349
- Add closure return types by @TomasVotruba in #16348
- Add parameter types to private methods based on local call sites by @TomasVotruba in #16364
- Resolve the remaining skipped cases in the Rector config by @TomasVotruba in #16368
- Add array parameter types to closures using array access by @TomasVotruba in #16370
- Narrow object return types by @TomasVotruba in #16372
- Keep setUp() protected as PHPUnit expects by @TomasVotruba in #16379
- Add return types to fluent methods by @TomasVotruba in #16376
- Remove duplicated @return docblocks and give event subscribers void returns by @TomasVotruba in #16387
- Simplify negated nested conditions by @TomasVotruba in #16407
- Use first-class callables in Twig extensions by @TomasVotruba in #16409
- Use readonly properties by @TomasVotruba in #16408
- Use first-class callables for function references by @TomasVotruba in #16416
- Use readonly classes by @TomasVotruba in #16417
- Add Response return types to controllers by @TomasVotruba in #16421
- Add known string parameter and return types by @TomasVotruba in #16424
- Use identical comparison for same-typed values by @TomasVotruba in #16429
- Add return types outside controllers by @TomasVotruba in #16422
- Run Rector across the whole project, including tests by @TomasVotruba in #16431
- Add type declarations to controllers by @TomasVotruba in #16437
- Add type declarations to models by @TomasVotruba in #16440
- Improve repository and model types by @TomasVotruba in #16438
- Use collection generics in docblocks and guard against type-breaking changes by @TomasVotruba in #16448
- Add type declarations to entities by @TomasVotruba in #16441
- Start the docblock coding standard cleanup and fix invalid @var names by @TomasVotruba in #16452
- Fill in missing docblock variable names by @TomasVotruba in #16455
- Standardize method and property docblocks on the multiline format by @TomasVotruba in #16457
- Add union return types and parameter types inferred from call sites by @TomasVotruba in #16454
- Fix docblocks with the variable name and type the wrong way round by @TomasVotruba in #16456
- Replace @inheritdoc with real types by @TomasVotruba in #16458
- Remove redundant setter comments by @TomasVotruba in #16461
- Apply the full docblock rule set by @TomasVotruba in #16464
- Put each promoted property on its own line for cleaner diffs by @TomasVotruba in #16466
- Apply the remaining type declaration rules by @TomasVotruba in #16467
- Use identical comparison for same-typed, boolean and strlen checks by @TomasVotruba in #16470
- Add known return types to repositories by @TomasVotruba in #16471
- Use readonly properties and first-class callables by @TomasVotruba in #16489
- Add array parameter types based on array access by @TomasVotruba in #16462
- Make variables inside strings explicit by @TomasVotruba in #16499
- Add missing return statements to non-void methods by @TomasVotruba in #16504
- Use readonly classes and typed properties, and drop a redundant getRepository() override by @TomasVotruba in #16490
- Use ExpressionBuilder or() and and() instead of the deprecated orX() and andX() by @TomasVotruba in #16505
- Avoid elseif after an early return or throw by @TomasVotruba in #16510
- Compare against null instead of calling is_null() by @TomasVotruba in #16519
- Make indirect variables explicit by @TomasVotruba in #16518
- Remove docblock returns superseded by native return types by @TomasVotruba in #16522
- Add explicit parentheses when instantiating anonymous classes by @TomasVotruba in #16520
- Drop the removed $_eventManager argument from Connection::__construct() by @TomasVotruba in #16506
- Improve generic type docblocks by @TomasVotruba in #16536
- Replace nullable docblocks with instanceof checks by @TomasVotruba in #16515
- Update the PHPStan baseline by @TomasVotruba in #16500
- Compare arrays against [] directly by @TomasVotruba in #16538
- Fix the deprecation for the Extension class namespace by @MrYamous in #16540
- Add a return type to getFunctions() to clear a deprecation by @MrYamous in #16541
- Add generics to CrmAbstractIntegration by @TomasVotruba in #16542
- Add a conditional return type to getModel() by @TomasVotruba in #16543
- Add getId() and return null where needed by @TomasVotruba in #16544
- Add a return type to Command::configure() to clear a deprecation by @MrYamous in #16555
- Rebuild the PHPStan baseline by @TomasVotruba in #16553
- Use first-class callables for count(), fwrite() and implode() by @TomasVotruba in #16534
- Fix remaining PHPStan level 3 errors by @TomasVotruba in #16545
- Add a return type to Twig's getFilters() by @MrYamous in #16560
- Add a return type to Twig's getTests() by @MrYamous in #16561
- Add a return type to Doctrine's getOrder() by @MrYamous in #16562
- Add the vote parameter to voteOnAttribute() by @MrYamous in #16566
- Update Rector to 2.5.7 and apply the rules it fixes by @TomasVotruba in #16554
- Apply self accessors and the full ECS control-structure rule set by @TomasVotruba in #16576
- Add a return type to getSubscribedEvents() by @MrYamous in #16568
- Add Response return types to the remaining controllers by @TomasVotruba in #16580
- Inject SmsModel instead of looking it up through ModelFactory by @TomasVotruba in #16582
- Inject the asset and contact models instead of looking them up by @TomasVotruba in #16587
- Inject the form and focus models as typed properties by @TomasVotruba in #16588
- Inject more models as typed properties by @TomasVotruba in #16589
- Enforce that autowire method names match their controller by @TomasVotruba in #16600
- Add types to private methods and properties by @TomasVotruba in #16564
- Move the custom Rector and PHPStan rules into /utils by @TomasVotruba in #16583
- Inject the stage model as a typed property by @TomasVotruba in #16597
- Inject the l* models as typed properties by @TomasVotruba in #16598
- Inject the c* models as typed properties by @TomasVotruba in #16595
- Inject the user role model as a typed property by @TomasVotruba in #16616
- Remove redundant method comments by @TomasVotruba in #16614
- Inject the report model directly by @TomasVotruba in #16620
- Inject the page model directly by @TomasVotruba in #16624
- Inject the plugin model directly by @TomasVotruba in #16625
- Inject the dynamic content model directly by @TomasVotruba in #16623
- Inject the point model directly by @TomasVotruba in #16622
- Put #[Required] setters on their own line to ease adding dependencies by @TomasVotruba in #16613
- Inject the dashboard, form submission, email and contact note models directly by @TomasVotruba in #16634
- Inject the point trigger, trigger event, contact field and point insight models directly by @TomasVotruba in #16635
- Inject the notification and page models directly by @TomasVotruba in #16617
- Inject the non-core notification model directly by @TomasVotruba in #16639
- Inject the remaining notification model directly by @TomasVotruba in #16640
- Update the PHPStan extension and fix the new reports by @TomasVotruba in #16638
- Report string-based getModel() calls in controllers by @TomasVotruba in #16641
- Inject the model in the mobile notification controller by @TomasVotruba in #16636
- Use model services directly in ReportController and others by @TomasVotruba in #16642
- Replace the remaining getModel() calls by @TomasVotruba in #16643
- Inject the model in LeadController by @TomasVotruba in #16656
- Inject the contact model in StageApiController by @TomasVotruba in #16648
- Remove an unused contact field model lookup in FieldController by @TomasVotruba in #16651
- Inject the model in CampaignApiController by @TomasVotruba in #16649
- Inject the model in EventLogApiController by @TomasVotruba in #16650
- Inject the model in the contact AjaxController by @TomasVotruba in #16652
- Inject the model in CompanyApiController by @TomasVotruba in #16653
- Inject the model in LeadApiController by @TomasVotruba in #16654
- Inject the model in ListApiController by @TomasVotruba in #16655
- Inject the model in NotificationApiController by @TomasVotruba in #16657
- Inject the model in the Clearbit PublicController by @TomasVotruba in #16658
- Use ImportModel directly in ImportController by @TomasVotruba in #16666
- Use the campaign model property directly in CampaignController by @TomasVotruba in #16667
- Skip traits in the string-based getModel() rule by @TomasVotruba in #16662
- Forbid mixing an #[Required] autowire method with a constructor by @TomasVotruba in #16663
- Inject MessageModel in MessageController by @TomasVotruba in #16668
- Inject the event model in EventController by @TomasVotruba in #16669
- Inject the event model in EventLogApiController by @TomasVotruba in #16670
- Inject the models in the Clearbit PublicController by @TomasVotruba in #16671
- Inject the contact models in CompanyApiController by @TomasVotruba in #16672
- Inject the page and trackable models in DynamicContentController by @TomasVotruba in #16673
- Inject the form models in FormApiController by @TomasVotruba in #16674
- Inject the form model in FieldController by @TomasVotruba in #16675
- Inject the contact models in LeadApiController by @TomasVotruba in #16677
- Inject the models in ListController, ListApiController and UserController by @TomasVotruba in #16689
- Inject the form models in SubmissionApiController by @TomasVotruba in #16681
- Inject the models in a batch of controllers by @TomasVotruba in #16679
- Add a rule to inject repositories in model classes instead of using the service locator by @TomasVotruba in #16645
- Finish migrating the last string-based getModel() call 🏁 by @TomasVotruba in #16584
- Add void return types to methods that return nothing by @TomasVotruba in #16695
- Import fully qualified class names by @TomasVotruba in #16694
- Remove redundant setter method comments by @TomasVotruba in #16706
- Use the #[AsCommand] attribute for Symfony 7.3 by @TomasVotruba in #16704
- Import fully qualified names by @TomasVotruba in #16717
- Treat the request stack as always present by @TomasVotruba in #16698
- Make protected members private in final classes by @TomasVotruba in #16700
- Access properties directly instead of calling internal getters by @TomasVotruba in #16701
- Use properties directly instead of throwaway variables in controllers by @TomasVotruba in #16711
- Use properties directly instead of throwaway variables in integrations by @TomasVotruba in #16713
- Use properties directly instead of throwaway variables in subscribers by @TomasVotruba in #16714
- Put autowire, required and attribute declarations on their own line by @TomasVotruba in #16718
- Remove a dead property and unreachable protected methods by @TomasVotruba in #16702
- Use properties directly instead of throwaway variables in models by @TomasVotruba in #16712
- Update the PHPStan baseline and drop fixed errors by @TomasVotruba in #16719
- Use properties directly instead of throwaway variables in the remaining classes by @TomasVotruba in #16715
- Mark controllers final to allow clearer type declarations by @TomasVotruba in #16603
- Add symplify/phpstan-rules with NoJustPropertyAssignRule by @TomasVotruba in #16716
- Import fully qualified class and constant references by @TomasVotruba in #16721
- Move command help text into the #[AsCommand] attribute by @TomasVotruba in #16723
- Use named arguments for nested validation arrays by @TomasVotruba in #16722
- Make always-provided services non-nullable in constructors by @TomasVotruba in #16703
- Warn on getRepository() calls with an exact class by @TomasVotruba in #16725
- Mark event subscribers final by @TomasVotruba in #16735
- Access properties directly instead of through their own getter by @TomasVotruba in #16740
- Use repository services directly in commands and controllers by @TomasVotruba in #16739
- Forbid string-based container->get() calls by @TomasVotruba in #16737
- Mark utils classes final by @TomasVotruba in #16752
- Forbid entity manager getRepository() calls with a class constant by @TomasVotruba in #16748
- Mark event classes final by @TomasVotruba in #16753
- Mark form type classes final by @TomasVotruba in #16754
- Mark bundle and compiler pass classes final by @TomasVotruba in #16756
- Inject repositories in plugins too by @TomasVotruba in #16757
- Mark exception classes final by @TomasVotruba in #16755
- Use more generic types to enable autowiring by @TomasVotruba in #16764
- Preserve the Twig Markup return type in OverrideIncludeExtension by @gustavocamalionti in #16741
- Add NoPropertyToPropertyAssignRule to catch property-to-property assignments by @TomasVotruba in #16766
- Inject repositories through the constructor instead of calling getRepository() by @TomasVotruba in #16768
- Move single config.php services to services.php by @TomasVotruba in #16758
- Forbid $this->get() calls with static values across the app and plugins by @TomasVotruba in #16771
- Finish injecting repositories instead of calling EntityManager::getRepository() by @TomasVotruba in #16773
- Tidy comments and formatting in object-manager.php by @israeldatascience in #16751
- Replace a compiler pass with #[AutowireIterator] by @TomasVotruba in #16776
- Move the campaign bundle services to services.php by @TomasVotruba in #16777
- Move 64 LeadBundle services to autowired services.php by @TomasVotruba in #16780
- Move 13 FormBundle services to autowired services.php by @TomasVotruba in #16783
- Move 8 SmsBundle services to autowired services.php by @TomasVotruba in #16785
- Move 9 ReportBundle services to autowired services.php by @TomasVotruba in #16786
- Move 3 PageBundle services to autowired services.php by @TomasVotruba in #16789
- Move 4 ApiBundle services to autowired services.php by @TomasVotruba in #16791
- Move 46 IntegrationsBundle services to autowired services.php by @TomasVotruba in #16781
- Move 5 UserBundle services to autowired services.php by @TomasVotruba in #16784
- Move 3 InstallBundle services to autowired services.php by @TomasVotruba in #16787
- Move 7 services across 7 bundles to autowired services.php by @TomasVotruba in #16798
- Move 2 services in CacheBundle and NotificationBundle to autowired services.php by @TomasVotruba in #16800
- Move 11 services across 6 bundles to autowired services.php by @TomasVotruba in #16799
- Move services in 10 bundles from config.php to autowired services.php by @TomasVotruba in #16810
- Move 13 CoreBundle file and string helper services to autowired services.php by @TomasVotruba in #16813
- Move 13 CoreBundle update services to autowired services.php by @TomasVotruba in #16812
- Make event class properties private by @TomasVotruba in #16821
- Make form type class properties private by @TomasVotruba in #16822
- Make dashboard subscriber properties private by @TomasVotruba in #16823
- Make controller properties and methods private by @TomasVotruba in #16824
- Move the asset, install, marketplace and user bundle services to services.php by @TomasVotruba in #16825
- Move the channel, dynamic content, integration, notification and report bundle services to services.php by @TomasVotruba in #16826
- Move around 25 CoreBundle, page, Monolog and env processor services to autowired services.php by @TomasVotruba in #16818
- Apply the full privatization rule set by @TomasVotruba in #16842
- Put constructors and autowire methods on their own line by @TomasVotruba in #16844
- Move the remaining config.php services to autowired services.php by @TomasVotruba in #16843
- Use getUserIdentifier() instead of the entity-only getUsername() by @TomasVotruba in #16856
- Move the last core, SMS and user bundle services to services.php 🥳 by @TomasVotruba in #16861
- Fill in parameter types to match parent implementations by @TomasVotruba in #16860
- Import all fully qualified names and add known property types by @TomasVotruba in #16876
- Convert event listeners to subscribers and fix ExceptionListener being registered twice by @TomasVotruba in #16879
- Mark childless controllers final by @TomasVotruba in #16881
- Mark childless repositories final by @TomasVotruba in #16882
- Mark childless helpers final by @TomasVotruba in #16883
- Mark childless DI extensions final by @TomasVotruba in #16884
- Mark childless console commands final by @TomasVotruba in #16885
- Mark childless validators and constraints final by @TomasVotruba in #16887
- Mark childless DAO and DTO classes final by @TomasVotruba in #16888
- Forbid parent constructor forwarding and convert 8 classes to autowire methods by @TomasVotruba in #16880
- Add the type-perfect rule set and use instanceof instead of empty() and isset() on objects by @TomasVotruba in #16850
- Update Rector to 2.5.9 and drop defaults on always-assigned properties by @TomasVotruba in #16890
- Mark childless channel and messaging classes final by @TomasVotruba in #16897
- Mark childless data layer classes final by @TomasVotruba in #16894
- Mark childless core infrastructure and security classes final by @TomasVotruba in #16898
- Remove useless re-casting outside the contact bundle by @TomasVotruba in #16900
- Mark childless segment, campaign and stats classes final by @TomasVotruba in #16896
- Remove unnecessary re-casting in entities and repositories by @TomasVotruba in #16903
- Remove unnecessary re-casting in subscribers and models by @TomasVotruba in #16904
- Mark the remaining childless classes final by @TomasVotruba in #16902
- Apply the code quality rule set by @TomasVotruba in #16918
- Inject repository services directly instead of calling Model::getRepository() by @TomasVotruba in #16923
- Use services directly instead of passing them between methods of the same class by @TomasVotruba in #16908
- Add the Symfony rule sets to the Rector config by @TomasVotruba in #16859
- Mark the last round of classes final by @TomasVotruba in #16931
- Mark the last two classes, LeadFieldEvent and DsnTransformer, final by @TomasVotruba in #16940
- Mark PlainTextHelper final and give it private, typed properties by @TomasVotruba in #16941
- Use boolean operators as intended and compare properties directly by @TomasVotruba in #16947
- Use self instead of static for local references by @TomasVotruba in #16956
- Compare empty arrays against [] directly by @TomasVotruba in #16957
- Use validator attributes instead of loadValidatorMetadata() by @TomasVotruba in #16959
- Inline array returns, move constructor defaults to properties and drop redundant readonly markers by @TomasVotruba in #16955
- Make all constraints usable as attributes with named arguments by @TomasVotruba in #16974
- Autowire token storage in ConfigController instead of passing it to setLocale() by @TomasVotruba in #16976
- Require the #[Attribute] marker on Symfony constraint classes by @TomasVotruba in #16975
- Apply strict types where it is safe by @TomasVotruba in #16982
- Inject UserPasswordHasherInterface into UserModel instead of passing it to methods by @TomasVotruba in #16980
- Autowire the translator into CommonRepository instead of passing it around by @TomasVotruba in #16979
- Make a local static method non-static to match the rest of its class by @TomasVotruba in #16981
- Inject the logger through the constructor instead of a controller action parameter by @TomasVotruba in #16985
- Add Response return types to controller actions and enforce them with PHPStan by @TomasVotruba in #16971
- Raise type coverage and rebuild the baseline by @TomasVotruba in #16986
- Deprecate the unused EmojiMap classes by @escopecz in #16992
- Deprecate EmojiHelper by @TomasVotruba in #17031
- Split the ORM and DBAL published-by-date queries by @escopecz in #16970
- Remove the unreachable second null case in getSourceLists() by @darkdi in #17224
🤖 DevOps
- Fold the standalone Rector test run into the main Rector job by @TomasVotruba in #16549
🌱 Plugin support
- Refactor the Clearbit plugin by @matbcvo in #16586
- Migrate the Tag Manager plugin off the deprecated AbstractIntegration by @matbcvo in #16795
- Migrate the Gmail plugin off the deprecated AbstractIntegration by @matbcvo in #16847
🎨 User Experience / User Interface
- Migrate the stylesheets from LESS to SCSS by @andersonjeccel in #16143
✨ Features and enhancements
🧑💻 Automated tests
- Add more dynamic content test cases by @aarohiprasad in #16483
- Verify that GrapesJS landing page content persists by @patrykgruszka in #16727
🚀 Campaigns
- Stop a campaign sending emails past a chosen date and time by @aarohiprasad in #15832
- Give the campaign builder an infinite canvas by @andersonjeccel in #16127
- Show the campaign event ID in the campaign builder by @rohitpavaskar in #16103
- Make the contacts card in campaign details clickable by @andersonjeccel in #16360
- Make campaign condition results easier to understand in the contact history by @adiux in #15664
- Explain what data is lost before deleting a campaign by @andersonjeccel in #16846
- Recover stuck campaign events more reliably by @rohitpavaskar in #17104
📚 Categories
- Explain what happens when you delete a category by @andersonjeccel in #16830
🏢 Companies
- Assign company owners in bulk by @andersonjeccel in #16853
👥 Contacts
- Merge tags by @andersonjeccel in #15331
- Warn about duplicate stage weights by @andersonjeccel in #15404
- Find and replace contact and company field values in bulk by @andersonjeccel in #16207
- Merge stages by @andersonjeccel in #15399
- Show upcoming events on the contact timeline by @andersonjeccel in #15871
⬆️ Dependencies
- Add Dependabot configuration for Composer and npm by @matbcvo in #16219
- Update Rector to 2.5.2 and remove unused skips by @TomasVotruba in #16311
🧑💻 Developer experience
- Document the Assets directory structure in AGENTS.md by @kuzmany in #15783
- Dispatch events when emails and pages are published or unpublished by @fedys in #16244
- Improve the Codespaces setup by @fedys in #16381
- Run post-checkout actions in Codespaces by @fedys in #16405
- Raise the type coverage levels further by @TomasVotruba in #16415
- Include parent exception data when debugging and logging by @TomasVotruba in #16488
- Improve migrations and the audit log by @rohitpavaskar in #16546
- Sort by click count in email details by @andersonjeccel in #16129
- Make segment links clickable in email details by @kuzmany in #15760
- Add A/B testing for segment emails by @kuzmany in #15897
- Preview an email from its details page by @andersonjeccel in #15692
- Add an email settings field by @fedys in #16363
- Support owner field tokens in tracked email URLs by @shinde-rahul in #15924
- Use the contact owner for Reply-To on queued email sends by @shinde-rahul in #15945
- Show the segments used on segment email detail pages by @andersonjeccel in #16593
- Send an example email straight from the email list by @andersonjeccel in #16871
📋 Forms
- Set a submission limit per form by @andersonjeccel in #15396
- Add a rating field to the form builder by @andersonjeccel in #15342
- List the contacts associated with a form in its details by @andersonjeccel in #16355
- Return to the form results after viewing a contact by @andersonjeccel in #16873
🍇 GrapesJS Builder
- Set image links in the GrapesJS page builder by @patrykgruszka in #16267
- Normalize protocol-less image link URLs in the GrapesJS builder by @patrykgruszka in #16369
- Add typography blocks to the GrapesJS email builder by @andersonjeccel in #15694
- Improve theme inheritance for background color and styling in the GrapesJS email builder by @irfanhanfi in #16403
⇅ Import/export
- Notify you when an import is canceled by @andersonjeccel in #15380
📣 Marketing messages
🔔 Notifications
- Update the notification preview as you edit by @andersonjeccel in #16928
🚄 Performance and optimization
- Index last_active on contacts by @rohitpavaskar in #16548
- Speed up campaign processing for newly added contacts by @adiux in #16759
🌱 Plugin support
- Add extra methods for custom plugins by @rohitpavaskar in #16481
- Add an event for plugins with complex permission logic by @escopecz in #16837
- Add a new Marketplace API with reviews and ratings by @vukovicpredrag in #15904
🎯 Points
- Add point insights by @JonasLudwig1998 in #15359
- Add onboarding for point insights by @PatrickJenkner in #16465
- Add a point trigger for contact stage changes by @shinde-rahul in #17041
📊 Reports
- Move the report date filter above the table by @andersonjeccel in #16204
- Add quick date filters to reports by @andersonjeccel in #16205
🌍 Segments
- Order preference center segments by public name by @shinde-rahul in #15850
- Add 'in the last' and 'in the next' segment filters by @fedys in #16574
🔎 Themes
- Add translatable tags to themes by @andersonjeccel in #15691
- Make the core themes compatible with theme inheritance by @JonasLudwig1998 in #16778
👣 Tracking
- Split
mtc.jsinto essential and tracking scripts by @patrykgruszka in #16660
🎨 User Experience / User Interface
- Remember the active tab on reload by @andersonjeccel in #15362
- Support tooltips on sortable and non-sortable table headers by @adiux in #16517
- Fix how restricted configuration fields render and are removed by @shinde-rahul in #15912
- Take the timezone from the browser instead of guessing it by @kuzmany in #12751
- Add a menu item for supporting Mautic by @escopecz in #16337
- Open the only entity type a user has permission for by @andersonjeccel in #16927
👤 Users and Roles
- Invite users by email by @andersonjeccel in #15401
- Add dedicated permissions for contact notes and migrate existing roles by @shinde-rahul in #16067
- Clone roles by @andersonjeccel in #15402
- Return only the contacts a user has permission to see by @shinde-rahul in #16022
🐛 Bugs
💻 API
- Fix OAuth bearer authentication for user-bound API v2 tokens by @escopecz in #16245
- Fix publish dates returned by the API by @escopecz in #16264
- Fix the dashboard API endpoint found by the API library tests by @escopecz in #16498
- Restore the missing OAuth 1 deleteAccessTokens() method by @TomasVotruba in #16516
🧑💻 Automated tests
- Verify Mautic against the API library integration suite by @TomasVotruba in #16532
- Improve the end-to-end acceptance test setup by @matbcvo in #16729
- Update stale PHPUnit and Symfony testing documentation links by @santhiprakash in #16855
- Backport the asset test fix by @escopecz in #17214
🚀 Campaigns
- Fix restarting a campaign by @escopecz in #16184
- Fix the Update Contact Owner event in the campaign builder by @rohitpavaskar in #16150
- Fix an infinite reschedule loop in ScheduledExecutioner::validateSchedule() by @TowyTowy in #16066
- Check edit permission when toggling a contact's campaign membership by @aarohiprasad in #15761
- Stop a campaign restarting on every cron run after it is republished by @escopecz in #16183
- Fix cloning campaign events by @andersonjeccel in #16254
- Fix event tooltips in the campaign builder by @patrykgruszka in #16289
- Handle zero-padded campaign time values on PHP 8.3 by @Thecesar85 in #16241
- Stop an infinite email resend loop after deleting a campaign decision by @hluchas in #16384
- Restore the campaign builder controls after a failed update by @andersonjeccel in #16874
- Keep the selected campaign email after inline editing by @patrykgruszka in #16972
- Fix inconsistent URL filter matching across campaign decisions and points by @shinde-rahul in #15979
- Stop the campaign trigger date shifting on every save by @hluchas in #17091
- Fix saving the Send a webhook campaign event by @rohitp19 in #16899
📚 Categories
🏢 Companies
- Improve how contact and company relationships are handled by @fedys in #16176
- Transfer company associations to the winning contact when merging by @adiux in #16247
👥 Contacts
- Fix creating tags in bulk with Enter by @andersonjeccel in #16206
- Fix a crash in the contact timeline when an asset has been deleted by @EnableServices in #16078
- Show who created a contact note by @shinde-rahul in #17177
📌 Dashboards
- Allow scrolling in dashboard widgets when the content overflows by @VedantMadane in #15888
⬆️ Dependencies
🧑💻 Developer experience
- Stop the message of the day from blocking by @escopecz in #16226
- Fix the offline error page when the working directory differs from the Mautic root by @MoonFuji in #16155
- Fix the Symfony debug bar by @hluchas in #16380
- Skip constant type coverage in PHPStan by @TomasVotruba in #16463
- Fix a TypeError in OverrideIncludeExtension with Twig 3.28 by @TowyTowy in #16487
- Fix a fatal error from colliding autowire() methods by @TomasVotruba in #16596
- Fix the PHPStan identifier for a Mautic property by @TomasVotruba in #16615
- Stop local.php values silently overriding .env and .env.local by @suparikoli in #17072
- Fix Composer asset issues by @escopecz in #17023
- Fix migration metadata initialization during install by @patrykgruszka in #17127
- Use the configured site URL for links generated in subrequests by @patrykgruszka in #17168
- Fix the CLI installer warning about the minimum limit by @peculiaruc in #17100
- Fix a replacment typo by @vaibhav8a in #17178
🤖 DevOps
- Update the Codecov action from v5 to v7 (7.2) by @escopecz in #16211
- Update the Codecov action from v5 to v7 (7.1) by @escopecz in #16210
🔄 Dynamic content
📝 Editor
- Stop CKEditor wrapping heading content in
<p>after editing the HTML source by @patrykgruszka in #16362 - Use CKEditor's built-in translations instead of a plugin by @escopecz in #16357
- Fix date filters on email graphs by @fedys in #16225
- Hide the pending stat card for triggered emails by @andersonjeccel in #16304
- Validate the retry strategy numbers in the queue settings by @MoonFuji in #16157
- Stop prompting for a backup after saving an email with no changes by @hluchas in #16383
- Stop the broadcast cron crashing on malformed links by @Thecesar85 in #16256
- Do not fail when no monitored address is set by @biozshock in #16302
- Stop treating HEAD requests as unsubscribes by @Dominic-Mayers in #14967
- Fix the stale pending count in the email overview by @hluchas in #16442
- Allow saving emails containing #, tel: and mailto: links by @JonasLudwig1998 in #16508
- Preserve the existing mailer DSN during migration by @patrykgruszka in #16925
- Restore VERP bounce routing broken by the Sender header in 7.1 by @EnableServices in #16152
📋 Forms
- Fix specific form results not being deleted from the form_results table by @rohitpavaskar in #16123
- Fix responsiveness of form fields by @andersonjeccel in #15425
- Fix conditional form sub-fields sharing a duplicate field order on save by @hluchas in #16223
- Fix the form field column layout breaking on submit and page breaks by @andersonjeccel in #16281
- Fix select and country field autofill in cached form HTML by @MoonFuji in #16179
- Fix comma-separated addresses in the form Send email action by @andersonjeccel in #16271
- Fix the visibility of captcha honeypot fields by @hluchas in #16514
- Fix selecting a zero-value default in a radio group by @adiux in #16637
- Make the To field mandatory in the Send form results action by @peculiaruc in #17137
- Prevent duplicate public form submissions by @shinde-rahul in #17138
- Use the configured site URL for form embeds without changing admin links by @patrykgruszka in #17193
🍇 GrapesJS Builder
- Re-enable the typography section and styling tools in the GrapesJS builder by @patrykgruszka in #16098
- Fix handling of headings, spans and paragraphs in the GrapesJS CKEditor integration by @GoodPHP in #16058
- Fix image link traits missing on pre-existing images in the page builder by @patrykgruszka in #16330
- Fix loading an email theme with an empty
mj-headin the GrapesJS builder by @patrykgruszka in #16334 - Fix editing tables in the GrapesJS CKEditor by @patrykgruszka in #16476
- Fix GrapesJS CKEditor splitting paragraphs on inline spans by @patrykgruszka in #16480
- Preserve MJML comments when dropping sections by @santhiprakash in #16765
⇅ Import/export
- Remove the footer z-index that broke the CSV import by @escopecz in #16482
- Add an import toggle to skip creating new contacts and companies by @JonasLudwig1998 in #17176
- Restore the entity fields the import was silently dropping by @vukovicpredrag in #17226
🌐 Landing pages
- Prevent text overlapping in landing page columns on mobile by @patrykgruszka in #16423
🚄 Performance and optimization
- Stop loading CKEditor assets on pages with no rich text field by @adiux in #16987
- Stop persisting the same summary batch repeatedly by @shinde-rahul in #16969
🌱 Plugin support
- Fix the plugin feature settings tab and other tabs by @JonasLudwig1998 in #15957
- Accept posted integration field pagination data by @TechEnthusGH in #16202
📊 Reports
- Fix empty filters breaking the report view by @andersonjeccel in #16125
- Omit non-grouped columns from SELECT when GROUP BY is active by @santhiprakash in #17099
🌍 Segments
- Fix the emails read segment filter by @hluchas in #16224
- Fix building segment membership with --max-contacts by @hluchas in #17027
🕵️ Search
- Fix alias-based quick filter handling by @shinde-rahul in #16027
- Reset the search index so keyboard navigation works reliably by @shinde-rahul in #15699
🔎 Themes
- Preserve runtime-backed Twig filters in the theme sandbox by @wizard770 in #16238
- Fix issues with theme inheritance by @JonasLudwig1998 in #16892
👣 Tracking
- Stop local IP lookup services crashing the system by @biozshock in #16137
🎨 User Experience / User Interface
- Revert the SCSS syntax refactoring that broke the UI by @andersonjeccel in #16340
- Wrap autoComplete.js in an IIFE to prevent RequireJS conflicts by @Hugo-Prossaird in #15660
- Stop status labels pulsing when the status is stable by @andersonjeccel in #16351
- Hide the label divider when only the publish status label is shown by @andersonjeccel in #16356
👤 Users and Roles
New Contributors
- @MoonFuji made their first contribution in #16180
- @TowyTowy made their first contribution in #16066
- @Thecesar85 made their first contribution in #16241
- @wizard770 made their first contribution in #16238
- @MrYamous made their first contribution in #16540
- @gustavocamalionti made their first contribution in #16741
- @santhiprakash made their first contribution in #16765
- @israeldatascience made their first contribution in #16751
- @dsp76 made their first contribution in #16772
- @VedantMadane made their first contribution in #15888
- @TechEnthusGH made their first contribution in #16202
- @suparikoli made their first contribution in #17072
- @peculiaruc made their first contribution in #17137
- @vaibhav8a made their first contribution in #17178
- @darkdi made their first contribution in #17224
Full Changelog: 7.2.0-rc...7.2.0
SHA1(7.2.0.zip)= c7afc576be22387e78993cedaba488dd9da927f5
SHA1(7.2.0-update.zip)= 2aa3628844441498fd142f00daba6fd8357d155a