Skip to main content

Porting

4 min read

Porting

Shared across every PremiumPress product. 7 classes in inc/Porting.

AdminPage AdminPage.php​

Import / Export screen for listings. Import is a three-step flow held together by one option row: upload a file, confirm the column mapping (pre-filled from a detected preset), then run. The run itself is driven from the browser in batches over AJAX rather than in the upload request — a 10,000-row file cannot finish inside one PHP request, and a half-finished import that died on a timeout is worse than one that reports progress and resumes. Export streams straight to the browser, so it never builds the whole file in memory. Administrators only: a bad mapping can rewrite thousands of listings, so this is not exposed to members.

API
register()maxAssembled()chunkSize()menu()job()render()handleExport()handleUpload()ajaxChunk()errorText()handleCancel()ajaxStep()
inc/Porting/AdminPage.php

Exporter Exporter.php​

Listing EXPORT — writes the current listings out as CSV. The column set is the same canonical vocabulary {@see Importer} reads, so an export is a valid import: download, edit prices in a spreadsheet, upload again, and every row updates in place rather than duplicating (the SKU column carries the identity). Rows are streamed straight to the output handle in pages, so exporting 10,000 listings costs one page of posts in memory at a time rather than the whole table.

API
columns()stream()filename()
inc/Porting/Exporter.php

Feeds Feeds.php​

Saved import feeds — a URL plus its column mapping, re-imported on a schedule so a catalogue stays in step without anyone uploading a file. The download reuses {@see \Ppt\Compare\FeedFetcher}, which already streams a large (often gzipped) file to disk rather than into memory. Everything after that is the ordinary import path, so a scheduled run and a manual upload behave identically — including the SKU identity that makes a re-run update instead of duplicate. Scheduling mirrors the comparison ingest: a WP-Cron event is registered, but WP-Cron only fires on traffic, so a real site should drive it from a system cron hitting the token-guarded endpoint instead. Both funnel through {@see runFeed()}.

API
register()all()get()save()delete()token()runDue()runFeed()handleSave()handleDelete()handleRunNow()handleToken()
inc/Porting/Feeds.php

Images Images.php​

Sideloads image URLs from an import into the media library. Images are the expensive part of an import — a download and several image resizes per row — so this is opt-in, bounded per listing, and never fatal: a dead URL loses one picture, not the product. De-duplication matters more here than anywhere else in the importer. A catalogue re-imported nightly would otherwise download and store the same photograph every night, so each attachment records the URL it came from and a second import reuses it.

API
attach()findBySrc()
inc/Porting/Images.php

Importer Importer.php​

Listing IMPORT — turns a CSV/TSV/XML file into listings, and re-running the same file UPDATES rather than duplicates. Parsing is delegated to {@see Reader}, which streams CSV, TSV and XML through generators (and transparently gunzips), so a 10,000-row file never lands in memory at once. This class only decides what a row MEANS and how it is written. Identity: every row is keyed on its SKU, stored as post meta. An import looks the SKU up first — found means update, absent means insert — so a catalogue can be re-imported nightly without ever duplicating a product. Resumability: {@see run()} processes a bounded batch and returns the offset to continue from, so a huge file is walked across many short requests (the admin screen drives it over AJAX) instead of one request that would time out.

API
run()findBySku()resetSkuMap()
inc/Porting/Importer.php

Presets Presets.php​

Column presets for listing IMPORT — the known layouts a client is likely to arrive with, so a Shopify or WooCommerce export can be imported without hand-mapping a single column. A preset maps CANONICAL field => the source's column name(s). The first column that exists in the uploaded file wins, so a layout that has drifted between versions of the exporting app still lands. Anything not covered by a preset is mapped by the user on the import screen; {@see Mapper} treats both the same way. Canonical fields are deliberately small and shared by every product line: the shop meta (price/sku/stock) is written only when the active fork actually has it.

API
fields()required()all()get()detect()resolve()
inc/Porting/Presets.php

Reader Reader.php​

Row reader for listing imports. Neither half of Compare\FeedParser suits a product export. Its CSV side reads a line at a time, which is right for merchant price feeds but wrong here — a Shopify "Body (HTML)" or a WooCommerce "Description" routinely contains newlines inside a quoted field, and a line-based reader splits one product across several broken rows. That silently creates junk listings instead of failing loudly. So CSV goes through PHP's own fgetcsv(), which understands quoted newlines and still reads incrementally — a 10,000-row file never lands in memory at once. Its XML side only recognises five hard-coded record names, so a catalogue of <article> or <listing> elements yields nothing at all; here the record element is detected from the document's own shape instead.

API
rows()header()
inc/Porting/Reader.php