Search by

baxtian / wp_importer

baxtian

Class to be inherite to create importers

Package info

bitbucket.org/baxtian/wp_importer

pkg:composer/baxtian/wp_importer

Statistics

Installs: 448

Dependents: 0

Suggesters: 0

0.3.54 2026-10-29 03:08 UTC

README

Base class for creating WordPress importers/exporters (CSV + XLSX). Appears under Tools → Import in the WordPress admin.

Usage

class MyImporter extends \Baxtian\WP_Importer
{
    use \Baxtian\SingletonTrait;

    protected function __construct()
    {
        $this->tipo   = 'product';        // singular slug
        $this->tipos  = 'products';       // plural slug
        $this->accion = 'import_products';// importer action slug

        add_action('init', [$this, 'init']);
        parent::__construct();
    }

    public function init(): void
    {
        $this->campos = [...];  // define here — translations are loaded
        $this->textos = [
            'import_button'       => __('Import Products', 'domain'),
            'import_file'         => __('Products file', 'domain'),
            'plural'              => __('products', 'domain'),
            'description'         => __('Import products from CSV or XLSX.', 'domain'),
            'mensaje_publicacion' => __('Created: %d, Updated: %d, Linked: %d', 'domain'),
            // ↑ Use commas as separators — JS splits by "," to build a bullet list
        ];
        // For custom DB tables, hook import/export here:
        add_filter('MrkImporter/import_data', [$this, 'handle_import'], 10, 4);
        add_filter('MrkImporter/export_data', [$this, 'handle_export'], 10, 4);
        parent::init();
    }
}

Important: define $campos and $textos inside init(), not the constructor — translations must be loaded first. Call parent::init() at the end of init().

Campo types

typedescription
idPost ID — used for matching on import
fieldpost_title or any direct post field
thumbnailFeatured image (URL on export, URL/path on import)
termTaxonomy term (comma-separated on export)
postmetaGeneric postmeta value
postmeta_boolBoolean postmeta — exports as 1/0
postmeta_textareaMultiline postmeta
postmeta_dateDate postmeta (Y-m-d)
postmeta_datetimeDatetime postmeta (Y-m-d H:i:s)
p2pPost-to-post relationship — requires 'p2p' key
ignoreHeader/export mapping only — no-op on import

Column headers

The file header is the key the importer matches each column by (rename_columns()), so it must not change between exporting and importing.

  • Default: a campo without title uses its name as the header (post_title, _barcode, provider). That is enough when the people who fill the file know these names.
  • title: add one only when the people who fill the file need friendlier names, and write it as a variable name in their language: lowercase, _ between words, no spaces or accents ('title' => 'precio_compra').
  • Never translate it (no __()): in wp-admin translations follow each user's language, and a translation fix or a missing .mo would change the header. A file exported by one user, or before a translation update, or on a site in another language, would be rejected. The textos are translated; the headers are identifiers.
  • Columns built from the site's data (one per warehouse, per attribute…): prefix:variable, with a fixed prefix and the variable part taken from a stable value such as a slug (cantidad:bodega_centro, atributo:talla). The importer recognises the family by the prefix, and two families cannot produce the same header. Columns that are not in campos are recognised with MrkImporter/column_name.

ignore

Declares a field that participates in rename_columns() (so the file header uses its title, see Column headers) and in export, but is never written by the built-in import switch — it's a no-op on import.

Use it when a column's real value must be set through business logic that can't go through a raw update_post_meta() — for example, a value that has to pass through a domain object, trigger side effects, or be resolved by a third-party listener. Hook into MrkImporter/row_added (fired after all other fields of the row are processed) to write the value yourself:

$this->campos = [
    ['name' => 'id',         'type' => 'id'],
    ['name' => 'post_title', 'type' => 'field'],
    ['name' => 'price',      'type' => 'ignore', 'title' => 'precio'],
];

add_action('MrkImporter/row_added', function ($post_id, $tipo, $line) {
    if ($tipo === 'product') {
        MyPricingService::set_price($post_id, $line['price']);
    }
}, 10, 3);

Hooks

HookTypeArgsWhen
MrkImporter/import_datafilter(false, $tipo, $data, $campos, $structureImporter, $step)Before importing — return ['nuevas'=>int, 'modificadas'=>int, 'terminos_nuevos'=>int] to replace built-in import; return false to fall through
MrkImporter/validatefilter([], $tipo, $filas, $campos)Step 1, before writing anything — return the list of problems found in the whole file; with any problem nothing is written
MrkImporter/column_namefilter(null, $column, $tipo, $direction, $campos)When a header is not in campos — return the field name that corresponds, or null
MrkImporter/avisosfilter($avisos, $tipo, $step)After each step — return the notices to show apart from the errors
MrkImporter/export_datafilter($data, $tipo, $tipos, $campos)Before writing the export file — return numeric array of rows (row 0 = header of campo names)
MrkImporter/row_addedaction($post_id, $tipo, $line)After each imported row (built-in importer only)
MrkImporter/attach_imageaction($attach_id, $attach_data)After each image is uploaded and processed
MrkImporter/upload_failedaction($uri)When a media URL could not be downloaded; the URL is skipped and no attachment is created

Rejected files and validation

  • A header that is not in campos (and that MrkImporter/column_name does not recognise) rejects the whole file before anything is written, naming the columns.
  • A row whose ID does not exist, or whose title matches several published items, is skipped and reported with its row number; an empty ID matches by title.
  • MrkImporter/validate receives the rows with the file row number as key (the header is row 1) and the field names as keys of each row:
add_filter('MrkImporter/validate', function ($errors, $tipo, $filas, $campos) {
    if ($tipo !== 'product') {
        return $errors;
    }
    foreach ($filas as $n => $fila) {
        if (!empty($fila['price']) && !is_numeric($fila['price'])) {
            $errors[] = sprintf('Row %d: invalid price "%s"', $n, $fila['price']);
        }
    }
    return $errors;
}, 10, 4);
  • Only one import of the same type runs at a time; a second one stops with a message and writes nothing.

Export format

handle_export must return a numeric array where row 0 is the header (campo name values) and rows 1…N are data rows (numeric arrays aligned with the header). rename_columns() translates names → titles for the downloaded file.

$rows   = [['name', 'email', 'webinars']]; // header
$rows[] = ['John', 'john@example.com', 'Webinar 1'];
return $rows;

XLSX date cells

Cells formatted as dates in Excel are automatically converted to Y-m-d H:i:s strings. No manual serial-number handling needed in the importer.

Access control

WordPress restricts Tools → Import to users with the import capability. Grant it explicitly for custom roles:

add_role('my_role', 'My Role', ['read' => true, 'import' => true]);

Maintainers

Juan Sebastián Echeverry baxtian.echeverry@gmail.com

Changelog

See CHANGELOG.md.