Repository navigation
[6.x] Importing - #19612
[6.x] Importing#19612i-just wants to merge 376 commits into
Conversation
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts: # resources/translations/en/app.php
|
This PR had conflicts with Claude's response
|
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PxhedKGW7eCLF4xLBVmh6o
An automatic merge from 6.x (038f1fd) dropped it, so Aliases::get() resolved to the nonexistent CraftCms\Cms\Asset\Aliases. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PxhedKGW7eCLF4xLBVmh6o
… feature/import
# Conflicts: # src/Cms.php
|
This PR had conflicts with Claude's response
|
# Conflicts: # resources/translations/en/app.php
|
This PR had conflicts with Claude's response
|
|
This PR had conflicts with Claude's response
|
# Conflicts: # src/FieldLayout/LayoutElements/Addresses/AddressField.php # src/FieldLayout/LayoutElements/Addresses/LatLongField.php # src/FieldLayout/LayoutElements/FullNameField.php # src/FieldLayout/LayoutElements/TitleField.php # src/FieldLayout/LayoutElements/Users/EmailField.php # src/FieldLayout/LayoutElements/Users/UsernameField.php
|
This PR had conflicts with Claude's response
|
# Conflicts: # resources/translations/en/app.php # src/FieldLayout/LayoutElements/CustomField.php
|
This PR had conflicts with Claude's response
|
# Conflicts: # resources/translations/en/app.php
|
This PR had conflicts with Claude's response
|
# Conflicts: # resources/translations/en/app.php # src/User/UserPermissions.php
|
This PR had conflicts with Claude's response
|
# Conflicts: # resources/translations/en/app.php
|
This PR had conflicts with Claude's response
|
# Conflicts: # src/Asset/Elements/Asset.php # src/Element/Concerns/HasStatuses.php # src/Element/Element.php # src/Entry/Elements/Entry.php # src/User/Elements/User.php
|
This PR had conflicts with Claude's response
|
It imports data from JSON, CSV or XML sources (local files or URLs) into Craft CMS elements or Eloquent models.
Core concepts:
Extensibility:
CraftCms\Cms\Import\Commands\ImportCommandData Types:
boot()viaCraftCms\Cms\Import\DataTypes\DataTypes::register(). To replace a built-in one,remove()it firstTransformers:
ElementTransformer=> the base element transformer, and the default for element importers without their own (in core, Users)EntryTransformer=> default when importing EntriesAssetTransformer=> default when importing AssetsElementTransformerauto-matches incoming keys to attributes and field handles even when the names aren’t exact (e.g.Plain Text/plain_text→plainText), and supports convention-basednormalize{PropName}()methods
myPlainTextField, or run complex checks to decide what valuemyOtherFieldgets. A transformer can be given as an instance or a class nameImporters:
ElementImporter(abstract) =>EntryImporter,AssetImporter,UserImporter. Each implementstargetClass(). Common settings:site(required) andfieldLayoutEntryImporteraddssectionandentryType, and can set an entry’s parent (parentId) in Structure sectionsAssetImporteraddsvolumeUserImporterwon’t import sensitive attributes likeadminorpassword; the only sensitive ones it allows areaffiliatedSiteId,email,passwordResetRequiredandphotoId.photoIdcan also be a URL or alocal path to the photo file
ModelImporter(abstract) =>SystemMessageImporteris the only built-in. Its mappable destinations are the model table’s columnsAddresshas no importer, so it’s never a standalone target. It can only be imported as nested content (Addresses field / User addresses)0disables batching)boot()viaCraftCms\Cms\Import\ImporterTypes::register()Field import handlers:
CraftCms\Cms\Import\FieldHandlers\FieldImportHandlerInterface, so the field itself needs no import code$importer->afterItemImported())boot()viaCraftCms\Cms\Import\FieldHandlers\FieldImportHandlers::register(). A field uses the handler for its own class or its nearest parent classAssetsFieldImportHandlerlets an Assets field value mix asset IDs with URLs and local file paths, creating assets from the files in the field’s upload locationfileConflictsetting decides what happens when a file with the same name already exists: use the existing asset (default), replace its file, or create a new assetImport plans:
import_plans)boot()viaCraftCms\Cms\Import\ImportPlan::register('handle', fn () => new ImportPlanData()...), and committed to your repository. The factory onlyruns when the plans are first loaded, so registering them adds no work at boot and doesn’t stop
artisan config:cachefrom working. The registered handle becomes the plan’s handle. A plan that is invalid, or that sets adifferent handle, is skipped and logged. Registering the same handle twice throws;
remove()unregisters a plan@root-relative or aliased path, or a URLmap), or fill one from a template instead (templates)matchCriteria)clearableItems)keepMissingNestedElements)allowedPrivateHostsconfig setting; loopback, link-local and cloud metadataaddresses are always rejected. The downloaded file’s contents must match its data type
importDisk(a local temp folder by default; use a shared disk if queue jobs can run ondifferent
servers), and each later job reads only its own items. They’re cleaned up when the step finishes, and stale ones are removed by garbage collection. Otherwise (batching disabled, or a job that stops early on a source that
fits in one batch) a follow-up job reads the source again
Mapping UI
depth
{lists the source’s columns as variables ({title}, or{{ object['First Name'] }}for names that aren’t identifiers). Templates work inside nested mappings too, where{blocks.heading}is the current block’s value. They’re rendered in Twig’s sandbox, since saving import plans doesn’trequire admin, and checked when the mapping is applied and when the plan is saved
matchCriteria,clearableItems,andkeepMissingNestedElementsare reserved: incoming data using them, at any depth, is never offered as a column and can’t be read by a templatecanBeSet: false(e.g.,id,uid) can only be used for matching, not setMatch criteria — three tiers, last wins
matchCriteriakey on the row,[systemHandle => incomingKey].Also looks inside
$data['fields']. If the row has no value for that key, the value given is matched on as-is.additionalMatchCriteria(), already resolved to actual values.Values from tiers 1 and 2 are matched literally (query syntax like
*ornot 5isn’t applied); tier 3 values are used as given.Clearable items
An array shaped like the match criteria. If a field or attribute is
nulland explicitly appliedunset()entirely, so the existing value is left untouched.Importing:
triggerImportPlans){source}is a@root-relative or aliased path, or a URL):artisan craft:import:entries {source} [--site=] [--section=] [--entry-type=] [--transformer=] [--match-criteria=]artisan craft:import:assets {source} [--site=] [--volume=] …artisan craft:import:users {source} …artisan craft:import:system-messages {source} …--siteonly on multisite), and--match-criteriatakes JSON (invalid JSON is silently ignored)Making things importable:
ElementImporter/ModelImportersubclass viaImporterTypes::register()CraftCms\Cms\Support\Attributes\Importablemarks an element’s properties as importable. Its flags areexcludeFromUiMapping,isContainer,canBeMatchCriteria,canBeClearedandcanBeSetCraftCms\Cms\Element\Contracts\ImportableContainerPropertiesInterfacemust be implemented by an element type with a container property (isContainer: true), likeUser’s addressesCraftCms\Cms\FieldLayout\Contracts\ImportableFieldLayoutElementInterfacemakes field layout elements importableCraftCms\Cms\Field\Contracts\ImportableElementContainerFieldInterfacelets container fields be imported correctlyCraftCms\Cms\Import\FieldHandlers\FieldImportHandlerInterfaceadds import-specific handling for a field typeEvents:
ItemImporting/ItemImported,ImportPlanSaving/ImportPlanSaved,ImportDispatching/ImportDispatched. The*ingevents are cancellable; to cancel, a listener sets$event->isValid = false.ItemImportedalso carries the element or model the data was imported into.
Each import run also fires lifecycle events, all sharing the run’s
runId:ImportStarted,ImportStepStarted,ImportChunkStarted/ImportChunkFinished,ImportStepFinishedandImportFinished(the*Finishedevents report whether anything failed). CLI imports fire these too, apart from the chunk events.
Config:
importDisk=> the filesystem disk import runs store their parsed source data on (set it to a shared disk if queue jobs can run on different servers)allowedPrivateHosts=> hostnames a source URL may resolve to private IP addresses forPermissions:
There’s a permissions group
importwithviewImportPlansand, nested under it,saveImportPlans,deleteImportPlansandtriggerImportPlans.Related issues
CMS-2390