Repository navigation
6.x - #4124
Draft
lukeholder wants to merge 451 commits into
Draft
6.x#4124lukeholder wants to merge 451 commits into
lukeholder wants to merge 451 commits into
Conversation
lukeholder
marked this pull request as draft
September 23, 2025 07:42
lukeholder
added a commit
that referenced
this pull request
Aug 20, 2026
PR #4124 (head 6.x, base 5.x) stays open for the whole migration, so every push already triggers a pull_request run. The push: branches: [6.x] trigger was firing a second, redundant full CI run for the same commit.
…lidator to the new Ruleset system Commerce 6.0 only ships Install.php for fresh installs, so the 6 loose point-release migrations drafted in database/migrations/ are unnecessary: 4 are schema changes already present in Install.php's table definitions (catalog pricing queue table, orders.customerDeleted, the subscriptions userId FK cascade, order notices.noticeType), and 2 are one-time data repairs (product type permission renaming, allVariants->variants changedattributes cleanup) for bugs that don't exist in a fresh 6.0 schema. CouponsValidator.php (which nothing referenced anymore) is replaced by a closure rule on Coupon::getRules()['code'], checking case-insensitive uniqueness against every other coupon row. It validates per-coupon inside Coupons::saveDiscountCoupons()'s save loop rather than batch-checking the whole Discount.coupons array like the legacy validator did, since each coupon is validated then immediately persisted before the next one in the loop - which naturally catches same-batch duplicates too, without needing the legacy version's own-discountId exclusion workaround.
# Conflicts: # src/Http/Controllers/Settings/ProductTypesController.php
…ugin convention HasTranslations checks dirname(getBasePath()).'/lang' before falling back to src/translations, so plugin translations belong at the package root. Stripped the old copyright header from each locale file to match core's app.php style.
…, changelog Previous commit's git add -A failed silently on a stale pathspec before it could stage the header strip and the crowdin.yml/.codecov.yml/changelog updates, so they landed in the working tree but not the commit.
Replaces the StoreBehavior-dependent getGlobals() lookup with
Sites::getCurrentSite()->getStore(), the Site::macro('getStore', ...)
registered in Plugin::registerBehaviorMacros(). CP asset bundles and Twig
templates stay in src-yii2/ for now (separate, larger migration task).
Confirmed via cms-6's yii2-adapter (Yii2ServiceProvider::registerCraftVariableCompatibility()) that the legacy CraftVariable EVENT_INIT bridge only forwards getComponents(), not attached behaviors, so this class never reached the live craft Twig global under Craft 6. craft.commerce/ orders/products/variants are already fully served by the NewCraftVariable macros in src/Plugin.php. Verified live via TemplateManager::renderString() before and after removal.
The alias was left over from when this file needed to disambiguate from the legacy craft\web\twig\variables\CraftVariable; that legacy import (and the CraftVariableBehavior it backed) is gone now, so there's no collision. Also fixed a stale docblock describing the pre-deletion legacy mechanism.
…umers, add opt-in serialization fields Site/User/Address no longer support attachBehavior(), so StoreBehavior, StoreLocationBehavior, ValidateOrganizationTaxIdBehavior, CurrencyAttributeBehavior, CustomerBehavior, and CustomerAddressBehavior have been dead/orphaned code since earlier migration stages stripped their EVENT_DEFINE_BEHAVIORS wiring. All six are deleted; their functionality was already independently reimplemented via macros and event listeners registered in src/Plugin.php. Auditing CurrencyAttributeBehavior's *AsCurrency consumers surfaced two live regressions (Transaction and CatalogPricing were both missing getters their own callers already used, throwing UnknownPropertyException on the order-edit and Settings > Product Pricing CP screens) and a third-party BC gap (ShippingMethodOption/ OrderAdjustment were public API via the legacy behavior's magic __call, independent of Commerce's own in-repo callers) — all four now have explicit get*AsCurrency() getters matching the pattern already established for Order/LineItem/Purchasable/Product. Closed the serialization half of the defineFields()/defineRules() design gap (no cms-6 core hook exists for adding keys to User::toArray()/Address::toArray()) with four new opt-in, read-only custom field types using dbType(): null — real custom fields flow through toArray()/GraphQL automatically once attached to a field layout via the CP, verified live end-to-end. Also deleted the two legacy Codeception tests asserting attachBehavior()/getBehavior() semantics no longer possible on the new element classes, and cleaned up dangling IDE-hint-only references to the deleted classes.
Also fixes src/Catalog/Products.php, which was referencing the legacy craft\commerce\Plugin class instead of the new CraftCms\Commerce\Plugin.
CKEditor's own field class is still Yii2-based (not yet ported to Craft 6), so the listener still registers via the legacy yii\base\Event::on() mechanism -- there's no Laravel event to hook yet. Redactor isn't supported under Craft 6, so its registration is deleted outright rather than ported.
src/ previously had a three-way Records/Models/Data split per domain folder, with Models/ being an inconsistent mix of Eloquent persistence models and plain Yii2-style data/config objects. This collapses it to a consistent two-namespace convention: Models/ is Eloquent only (extends BaseModel), Data/ is everything else (extends Component), matching the existing Catalog/ProductType/Data and Order/LineItem/Data precedent. Records/ is gone. Also moves Tax\Models\EuVatIdValidator to Tax\VatValidator\Eu, pulling it out of this convention since it's a stateless validator, not a data object. Pure rename, no behavior changes, no BC shims (CraftCms\Commerce\* is still unreleased/alpha).
Ports craft\commerce\Plugin::_registerProjectConfigEventListeners() to CraftCms\Commerce\Plugin::registerProjectConfigEventListeners(), registering against app(CraftCms\Cms\ProjectConfig\ProjectConfig::class) instead of the legacy Craft::$app->getProjectConfig(). The new ProjectConfig::onAdd()/onUpdate()/onRemove() invoke handlers with ItemAdded/ItemUpdated/ItemRemoved (subclasses of the new CraftCms\Cms\ProjectConfig\Events\ConfigEvent), not the legacy craft\events\ConfigEvent, so every handler method this wires up had its type-hint updated to match. The legacy src-yii2/services/* shims keep accepting the legacy ConfigEvent on their own signatures for third-party BC, adapting it into the new type before delegating. Also modernized ProductTypes::pruneDeletedSite() to listen for the Laravel SiteDeleted event instead of the legacy craft\services\Sites::EVENT_AFTER_DELETE_SITE, and the project-config rebuild listener now uses the Laravel ProjectConfigRebuilt event. The Emails::EVENT_AFTER_DELETE_EMAIL listener stays on the legacy yii\base\Event::on() mechanism for now, since that event's firing hasn't been bridged to Laravel yet (see the TODO in Emails::handleDeletedEmail()).
Fixes the "UserQuery::EVENT_AFTER_POPULATE_ELEMENTS was removed in Craft 6" TODO in craft\commerce\Plugin::_registerCraftEventListeners(). The legacy event doesn't exist in Craft 6, and it turns out ElementPopulating/ElementPopulated aren't it either -- those are GraphQL-mutation-specific. The real equivalent is CraftCms\Cms\Element\Queries\Events\ElementsHydrated, fired from HydratesElements::hydrate() for every element type's query (not scoped to UserQuery like the old event was), so the listener filters for User instances itself. Ports the bulk-populate logic for primaryBillingAddressId/ primaryShippingAddressId to CraftCms\Commerce\Plugin, pre-warming ObjectState for a batch of hydrated Users so the getter macros (registerCustomerMacros()) don't each run their own per-user query.
Moves the customer-attach bulk-populate logic out of an anonymous closure in Plugin::registerCraftEventListeners() into a proper named method, Customers::elementsHydratedHandler(), matching the existing *Handler naming convention on that service (loginHandler, afterSaveUserHandler, etc). Registers it declaratively via a new Plugin::$events array instead (CraftCms\Cms\Plugin\Concerns\HasListeners, already inherited by every plugin but not yet used by Commerce), using Laravel's 'ClassName@methodName' listener-string syntax so the handler doesn't need to be named handle() or live in a dedicated listener class.
Swaps the ElementsHydrated => Customers::class.'@elementsHydratedHandler' entry for a real listener class, CraftCms\Commerce\Customer\Listeners\ElementsHydratedListener, matching the pattern cms-6's own HasListenersTest demonstrates as the intended convention. Plugin::$events now registers a bare class-string, so the phpstan-ignore needed for the magic string is no longer required.
Moves craft\commerce\Plugin::_registerPoweredByHeader() to CraftCms\Commerce\Http\Middleware\PoweredByHeader, pushed onto the craft middleware group in Plugin::boot() -- matching how core's own CraftCms\Cms\Http\Middleware\SetHeaders already handles the "Craft CMS" half of this header via the same group.
Moves schemaVersion/minVersionRequired/hasCpSettings/ hasReadOnlyCpSettings directly onto the new Plugin class, and relocates HasCommerceEditions to src/Plugin/Concerns/ (matching HasPermissions/HasServices) with typed constants and declare(strict_types=1). Fixes a real bug: $minCmsEdition was Edition::Solo (the HasEditions base-trait default) instead of Edition::Pro. The legacy craft\commerce\Plugin's own CmsEdition::Pro override masked this everywhere it's actually bootstrapped -- except the Pest suite, which boots CraftCms\Commerce\Plugin directly and was silently running with the wrong minimum edition. Removed the now-redundant declarations (plus an orphaned docblock) from craft\commerce\Plugin.
Converts craft\commerce\Plugin into a pure class_alias for CraftCms\Commerce\Plugin, matching every other fully-migrated legacy class in this migration (e.g. Transfers). Everything it still did gets ported, dropped as redundant, or dropped as dead/broken: - Ported getSettingsResponse()/getReadOnlySettingsResponse()/ createSettingsModel() onto the new Plugin. Added a getSettings(): ?Settings override narrowing HasSettings' base ?Validatable return type, fixing 25 phpstan errors that only surfaced once the settings chain was fully wired up. - Ported the relatedToProducts/relatedToVariants GraphQL argument definitions to the modern GqlArgumentsResolving event. - Dropped _registerGqlInterfaces()/_registerGqlQueries() entirely -- fully redundant with the already-shipped $gqlTypes/$gqlQueries arrays. - Ported the FK-restore utility registration and the one remaining legacy CP route registration as-is (both still legacy Event::on(), no Laravel equivalent exists yet for either). - Dropped beforeInstall()'s version-guard checks and the @commercelib alias -- both dead/redundant. - Found and dropped a pre-existing bug rather than porting it: the Purchasable-restore event registration calls a method, beforeRestorePurchasableHandler(), that doesn't exist anywhere in the codebase -- confirmed present in the shipping 5.x branch too. Restoring a trashed purchasable would throw a fatal error if this ever actually fired. Deleted src-yii2/plugin/Routes.php (and the now-empty src-yii2/plugin/ directory) -- fully absorbed into the above. Also split Plugin.php (previously ~990 lines) into two more Concerns traits: HasCommerceMacros (all Macroable macro registration) and HasCommerceEventListeners (all event listener registration, including everything ported above), matching the existing HasPermissions/ HasServices/HasCommerceEditions pattern.
Replaces the imperative Event::listen() calls in registerCraftEventListeners() with 8 dedicated classes under CraftCms\Commerce\Plugin\Listeners\*, registered declaratively via Plugin::$events -- matching the ElementsHydratedListener pattern. $events-based registration runs unconditionally (HasListeners has no gating mechanism), unlike the old method which only ran when $this->isInstalled. Each listener's handle() now starts with its own isInstalled guard to preserve that original protection.
# Conflicts: # src/Http/Controllers/Settings/ProductTypesController.php
[6.x] Render product sources in the secondary nav
# Conflicts: # src/Plugin.php
[6.x] Put nav into group
[6.x] Fix customize sources modal
…e controllers to Ui
…ource-naming Renamed controller methods to Laravel resource naming and added new cart action URLs
Test related to craftcms/cms#19943
[6.x] Use crypto.getRandomValues for custom line item UUIDs
[6.x] Port the order index to the Inertia element index
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
https://linear.app/craftcms/issue/COM-613/laravel-port