Documentation · Technical Documentation
Import Engine
This document describes how the ADP Car Market Hub plugin imports vehicle listings into WordPress. The plugin supports two import front-ends that share the same downstream pipeline:
- the AutoScout24 API importer (
AS24CI\Client→AS24CI\Importer), documented in detail below, and - the Connections front-end (internal class
AS24CI\Universal_Importer; the admin screen is named "Connections", slugas24ci-universal-import), which imports CSV / XML / XLSX / JSON / ZIP files and named connectors (feed, e-mail, carcuro, mobile.de). See The Connections importer below and the Connections admin reference.
This document covers the responsibilities of the importer classes, the per-listing flow, change detection, full-sync deletion and the public hook that fires after a vehicle is imported.
When to use this document
Read this document if you need to:
- Understand what happens during an import run.
- Diagnose why a vehicle was inserted, updated, skipped or removed.
- Build a custom integration that reacts to imports via the
as24ci_vehicle_importedaction. - Plan capacity or hosting requirements for large catalogues.
For scheduling and cron details, see Cron Events And Scheduler. For image handling, see Image Importer And Queue.
Overview
The import engine consists of several classes that work together:
AS24CI\Client— issues authenticated HTTP requests to the AutoScout24 API, manages OAuth tokens (including theas24ci_access_tokentransient cache) and exposes helpers for preview pages and per-listing equipment.AS24CI\Importer— coordinates per-listing import. Decides whether to insert, update or skip and writes the vehicle's post, postmeta and image references. Owns change detection.AS24CI\Mapper— maps the raw AutoScout24 payload to the fields stored in theas24_vehiclestable viaAS24CI\Vehicle_Repository. Maintains backwards-compatible postmeta keys.AS24CI\Vehicle_Repository— persists vehicle field data in the dedicated{$wpdb->prefix}as24_vehiclestable.AS24CI\Image_Importer— downloads images, optionally converts them to WebP, and attaches them to the vehicle post. See Image Importer And Queue.AS24CI\Scheduler— wrapsImporter::import_all_for_seller()in a locking, retry-safe runner used by WP-Cron, the REST cron endpoint and the manual "Run now" button.
The same Importer instance is reused by the manual Batch-Wizard,
the cron runner and the REST cron endpoint.
Requirements or prerequisites
Before running an import you need:
- Valid AutoScout24 API credentials configured in the plugin
(
as24ci_base_url,as24ci_token_url,as24ci_client_id,as24ci_client_secret,as24ci_token_audience). - One or more seller IDs in
as24ci_seller_ids(comma-separated). - A WordPress user designated as the default post author
(
as24ci_default_post_author); imports fall back to the current user when no default is set. - Working outbound HTTPS access from the WordPress server to the AutoScout24 API and to the image hosts referenced in the API response.
- Sufficient PHP execution time. The scheduler raises the time limit
to
300seconds viaset_time_limit(300)for cron and REST runs; some hosts may override this.
Per-listing flow
AS24CI\Importer::upsert_post_from_listing() is the central
per-listing routine. The simplified flow is:
- Validate the listing has an
id. Skip otherwise. - Skip listings whose API status is not
activatedor whoseliveflag is empty. - Look up an existing vehicle post by listing ID
(
find_post_id_by_listing_id()). - Run change detection (see below). If nothing has changed, refresh
the last-sync heartbeat via
AS24CI\Sync_State::touch()and returnskipped. - Build the post title, excerpt and content. If the AI Lock
(
_as24ci_ai_locked = 'yes') is active, do not overwrite the post excerpt or content. - Insert or update the WordPress post with the configured default post status and author.
- Write backwards-compatibility postmeta:
_as24ci_listing_id,_as24ci_seller_idand_as24ci_last_modified(when the API supplies it). The last-sync heartbeat is recorded separately viaAS24CI\Sync_State::touch()(a dedicatedas24ci_sync_statetable, not postmeta). - Ensure the slug ends with the listing ID
(
ensure_slug_has_listing_id()). - Map the listing payload via
AS24CI\Mapper::map_listing_to_post()so the typed fields are written to theas24_vehiclestable. - Fetch equipment via
Client::get_listing_equipment()and map it to the post viaMapper::map_equipment_to_post(). Errors are logged but do not fail the listing. - Import images (subject to the
as24ci_import_imagestoggle andas24ci_max_imagescap). The image flow is described in detail in Image Importer And Queue. - Update
_as24ci_content_hashand_as24ci_original_description. - Fire the
as24ci_vehicle_importedaction with the post ID, the raw API listing and a boolean$is_update. - Return one of the strings
inserted,updated,skippedorerror(onwp_insert_post/wp_update_postfailure).
Change detection
The importer skips listings that have not changed since the previous run, using two cascading checks:
- Primary: the
lastModifiedDatefield returned by the API. If the local_as24ci_last_modifiedpostmeta matches the remote value, the listing is treated as unchanged. The importer still refreshes the last-sync heartbeat (AS24CI\Sync_State::touch()) for the Importer admin tab. This heartbeat is purely informational; the full-sync deletion logic relies on listing-ID membership, not on the last-sync timestamp. - Fallback: when
lastModifiedDateis missing, the importer computes an MD5 of the JSON-encoded payload and compares it against the stored_as24ci_content_hash.
Image change detection runs independently and is described in the image importer documentation.
AI Lock
When _as24ci_ai_locked is 'yes', the importer keeps the existing
post_content and post_excerpt so AI-generated copy survives a
re-import. All other fields, taxonomy assignments, equipment and
images are still updated. The original API description is also
preserved separately in _as24ci_original_description.
Slug stability
ensure_slug_has_listing_id() updates the post slug to end with
-<listing_id> whenever the slug changes. This keeps URLs stable
across re-imports and avoids accidental redirects when titles change.
Bulk operations
AS24CI\Importer exposes the following high-level methods:
get_seller_ids()— returns the cleaned list of configured seller IDs.get_all_preview_listings_for_seller( $seller_id, $max_pages = 20, $page_size = 50 )— pages through the API and returns the full set of activated, live listings for a single seller.preview_listings_for_seller( $seller_id, $page = 1, $per_page = 50 )— paginated preview used by the wizard's UI.import_selected_listings( $seller_id, $listing_ids, $max_vehicles = 0 )— imports the given listing IDs. Honours the$max_vehiclesbudget; skipped vehicles do not consume budget.import_all_for_seller( $seller_id, $max_vehicles = 0 )— fetches the full preview list for a seller and delegates toimport_selected_listings(). Returns counts pluslisting_ids(the full set seen this run) andapi_active(number of active listings the API returned).full_sync_after_import( array $remote_listing_ids )— deletes localas24ci_carposts whose_as24ci_listing_idis not in the given remote set. Skipped if the remote set is empty as a safety guard against accidental wipe.upsert_external_listing( array $listing, string $source_id )— Universal-Importer entry point: tags the vehicle with the_as24ci_sourcepostmeta and routes the AutoScout24-shaped listing through the sameupsert_post_from_listing()flow. Returnsinserted,updated,skippedorerror.full_sync_for_source( string $source_id, array $external_ids )— full sync scoped to a single connector source: **permanently deletes** only that source's vehicles missing from the latest run, never touching AutoScout24 vehicles or another source. Returns{ kept, deleted }(a legacyheldkey may still appear in the signature but holding is no longer used — see Soft-delete removed); a no-op on an empty source or id.hard_delete_listing( $listing_id )— manual destructive delete for a single listing; goes through the sameVehicle_Deletercleanup as native WordPress delete.
The "All-in-One" version of the plugin imposes no listing cap;
get_free_import_limit() and get_free_slots_left() return
PHP_INT_MAX.
The Connections importer
The Connections importer (internal class name AS24CI\Universal_Importer;
the admin screen is labelled "Connections") brings non-AutoScout24
inventory into the same pipeline. It is format-agnostic and
source-agnostic: any source row is first turned into an
AutoScout24-shaped $listing array and then fed into the same
AS24CI\Importer used for the API import, so VIN re-listing detection,
value normalization, image import, slug stability and team assignment
all behave identically.
Reader and format dispatch
AS24CI\Import_Reader::read( string $content, string $filename = '' )
returns a uniform { header, rows } structure and dispatches by
format to a per-format reader:
| Format | Reader |
|---|---|
| CSV / TSV / TXT | AS24CI\Import_Csv_Reader |
| XML | AS24CI\Import_Xml_Reader |
| XLSX | AS24CI\Import_Xlsx_Reader |
| JSON | AS24CI\Import_Json_Reader |
| ZIP | AS24CI\Import_Zip_Reader |
Import_Reader::detect_format() resolves the format from the filename
extension first, then falls back to content sniffing (ZIP magic
bytes — distinguishing an Office .xlsx from a data+images bundle —
then a leading < for XML or {/[ for JSON, otherwise CSV). The
accepted extensions are csv, txt, tsv, xml, xlsx, json, zip
(Import_Reader::ACCEPTED_EXTENSIONS).
- The CSV reader normalizes the encoding to UTF-8 first (see
below), auto-detects the delimiter (comma / semicolon / tab) and
parses with
fgetcsvon a memory stream so quoted fields that contain the delimiter, quotes or line breaks (multi-line descriptions) are read correctly. AS24CI\Import_Encoding::to_utf8()converts DACH-region exports (Windows-1252 / ISO-8859, UTF-16/32 with a BOM) to clean UTF-8. The XLSX reader does not use it (its inner XML is always UTF-8).- The ZIP reader reads the first supported data file inside the
archive, extracts bundled images into an
as24ci-import/…folder underwp-content/uploads, and rewrites bare image filenames in the rows to the resulting public URLs.
Mapping template
AS24CI\Import_Template turns one raw source row into an
AutoScout24-shaped listing. Canonical field names are the AutoScout24
field names (makeKey, price, …) used by AS24CI\Field_Mapping and
AS24CI\Mapper. The template:
- maps configured source columns → canonical fields via
field_map; - normalizes numeric fields:
INTEGER_FIELDS(drops thousands separators such as.,', spaces and trailing units) andFLOAT_FIELDS(accepts a decimal comma, dot output); - applies per-field
value_maps(lower-cased source value → canonical value) and coerces recognised boolean fields; - builds a stable
id: dealer stock number →VIN-<normalised VIN>→ROW-<row hash>; - splits the configured image column on the image delimiter and
promotes scheme-less URLs (e.g.
www.example.com/a.jpg) to absolutehttps://; - forces
status = activatedandlive = trueso the downstream importer does not skip the row.
Run flow
AS24CI\Universal_Importer::run( $content, $template, $source_id,
$dry_run = true, $sample_limit = 5, $full_sync = false ):
- Parses
$contentviaImport_Reader::read(). - For each row, builds a listing via
Import_Template::to_listing()and runs deterministic validation (Universal_Importer::validate()— requires an identity, a make/model/version, a plausible VIN when present and a numeric price). - On a dry run, returns a sample preview (new vs. update) without writing.
- Otherwise writes each valid listing via
Importer::upsert_external_listing( $listing, $source_id ), which tags the vehicle with the_as24ci_sourcepostmeta and routes through the sharedupsert_post_from_listing(). Manual/file imports always import images immediately (never the queue). - When
$full_syncis true and a$source_idis set, callsImporter::full_sync_for_source()to permanently delete vehicles of THIS source that were absent from the run. Full-sync is opt-in because a manual upload is often only a subset of the inventory; it never touches AutoScout24 vehicles or another source.
Named connectors
Four connectors reuse the same template + reader to import on a
schedule, each storing a settings option plus (where applicable) an
encrypted secret option via AS24CI\Secrets:
AS24CI\Import_Feed— pulls a file from anhttps://,ftp://,ftps://orsftp://URL or a server file path (with Dropbox / Google Drive / Google Sheets / OneDrive share-link normalization), reusing the savedas24ci_import_csv_templatemapping. Runs on its own cron eventas24ci_import_feed_event.AS24CI\Import_Mail— connects over IMAP and imports the best matching attachment from the newest unread message. Runs on its own cron eventas24ci_import_mail_event.AS24CI\Import_Carcuro— pulls the carcuro public inventory API using a fixed built-in field mapping (Import_Carcuro::carcuro_template()), so no manual column mapping is required. carcuro'saccident_freeis inverted into the canonicalhadAccidentflag via avalue_map. Runs on the central import schedule (AS24CI\Scheduler::CRON_HOOK=as24ci_scheduled_import); it has no cron event of its own and is a no-op unless carcuro is enabled and a token is set.AS24CI\Import_Mobile(includes/class-as24ci-import-mobile.php) — the mobile.de connector. Imports the dealer's mobile.de inventory (JSON) through the built-inImport_Mobile::mobile_template(). Like carcuro, it runs on the central import schedule (as24ci_scheduled_import) rather than a per-source event, and itsrun()is a no-op unless mobile.de is enabled and credentials are present. Behaviour (schedule, full-sync) is governed centrally under "Import & Limits", exactly like AutoScout24.
The connectors expose a common
register_hooks() / run() / run_now() / execute() / finish()
surface. For their cron events see
Cron Events And Scheduler; credentials
are entered in the Connections admin tab
(internal class AS24CI\Admin_Tab_Universal_Import, slug
as24ci-universal-import), not in Settings.
Vehicle deletion
Every permanent delete path flows through AS24CI\Vehicle_Deleter:
- WordPress native "Delete permanently" on the Cars list.
- Importer full sync (
full_sync_after_import()). - Bulk actions (
AS24CI\Bulk_Actions). - Manual
Importer::hard_delete_listing().
The deleter runs on before_delete_post / deleted_post and:
- Removes attachments tracked in
_as24ci_image_ids. - Removes the corresponding row in
{$wpdb->prefix}as24_vehicles. - Fires the
as24ci_vehicle_deletedaction.
Manual gallery attachments (_as24ci_manual_image_ids) and other
attachments not tracked by the importer are intentionally preserved.
Alongside the deleter, AS24CI\Vehicle_Redirects hooks before_delete_post
at priority 5 (i.e. before the deleter) so the soon-to-be-removed
vehicle's slug and as24ci_brand / as24ci_model term slugs are captured
while the post and its terms still exist. The deleted URL is then
301-redirected to a live make/model search on the next request instead of
returning a 404 — see
architecture overview for the full mechanism.
Soft-delete removed
Earlier versions held source-removed vehicles in a dedicated
as24ci_archived ("Removed from source") post status for a grace
period before deleting them. **This soft-delete feature has been
removed.** Full sync (full_sync_after_import() and
full_sync_for_source()) deletes source-removed vehicles
immediately through AS24CI\Vehicle_Deleter —
delete_post_with_attachments() calls
Vehicle_Deleter::delete_vehicle_permanently() unconditionally.
The AS24CI\Soft_Delete class, the as24ci_archived post status, the
daily cleanup cron as24ci_soft_delete_cleanup_cron and the
as24ci_sync_soft_delete / as24ci_sync_grace_days options **no longer
exist**.
A one-time data migration purges any remnants from the old model: it
permanently deletes any vehicles still parked in the as24ci_archived
status or carrying the _as24ci_soft_deleted_at postmeta (routed
through AS24CI\Vehicle_Deleter so attachments and the vehicles-table
row are cleaned up too) and unschedules the old
as24ci_soft_delete_cleanup_cron cron for installations upgraded from
older versions.
In short: with full sync enabled, a vehicle that disappears from its source is hard-deleted on the next run, not held.
Public hooks
The import engine fires the following hooks. Verify hook signatures in the current plugin version before relying on them.
| Hook | When it fires |
|---|---|
as24ci_vehicle_imported | After upsert_post_from_listing() finishes. Args: $post_id, $listing, $is_update. |
as24ci_vehicle_deleted | After a vehicle and its tracked attachments are removed via Vehicle_Deleter. |
The plugin also reads the as24ci_webp_quality filter when
converting images; see the image importer documentation.
Configuration reference
| Option | Effect | Default |
|---|---|---|
as24ci_seller_ids | Comma-separated seller IDs to import. | (none) |
as24ci_default_post_status | Post status for newly inserted vehicles. | draft |
as24ci_default_post_author | Default author user ID for inserted vehicles. | (none → current user) |
as24ci_import_images | Whether to import images. | (toggle in admin) |
as24ci_max_images | Max images per vehicle. 0 = no plugin-side cap. | 30 |
as24ci_full_sync | Delete local vehicles missing from the API after each import. | 0 |
as24ci_cron_image_queue | Use the image queue during cron/REST runs. | 1 |
as24ci_cron_max_vehicles | Vehicle cap per cron/REST run. 0 = unlimited. | (Scheduler default 50) |
as24ci_verbose_logging | Log a line per vehicle and image action. | 1 |
as24ci_mapping_overrides | Per-field label and visibility overrides used by the admin UI. | (empty) |
Operational notes
- The importer never throws on a single bad listing. Errors from
wp_insert_post,wp_update_post,Client::get_listing_equipmentand the image importer are logged viaAS24CI\Loggerand counted but do not abort the run. - Change detection means a full re-import of an unchanged catalogue is inexpensive: most listings are skipped and image downloads are not re-issued.
- The mapper writes to the
as24_vehiclestable via the repository, not towp_postmeta, except for the small backwards-compat key set documented in Data Model. - Full-sync is opt-in. If
as24ci_full_syncis enabled, ensure the configured seller IDs cover the entire catalogue you want to keep online; vehicles whose listings disappear from the API are permanently deleted. - Verbose logging produces detailed log lines and grows quickly on large catalogues. The plugin enforces a 10 MB log cap with rotation; adjust verbose logging to balance diagnostics with storage.
Troubleshooting
- No vehicles imported. Confirm the API credentials, that
as24ci_seller_idsis non-empty, and that the API returns listings withstatus = activatedandlive = true. Check the plugin log for HTTP errors fromAS24CI\Client. - Listings remain in
draftstatus. The plugin defaultsas24ci_default_post_statustodraftso administrators can review mappings. Change the option topublishonce you are satisfied, or publish individual vehicles from the admin list. - Repeated re-imports keep updating the same vehicles. Verify
that the API supplies a stable
lastModifiedDate. When it is missing, the fallback content hash detects changes; the hash will change whenever the API payload changes for any reason. - A vehicle disappeared after a cron run. Check whether
as24ci_full_syncis enabled. With full sync on, any listing missing from the API is permanently deleted (its imported attachments included). Importer ran but no images were downloaded.Checkas24ci_import_imagesandas24ci_max_images. In cron/REST runs withas24ci_cron_image_queue = 1, only the first image is downloaded immediately and the rest are deferred to the image queue worker.Skipping listing_id=… : lastModifiedDate unchanged.This is the expected verbose log line for unchanged listings; it confirms change detection is working.