-
Plugin settings — Settings model with
contentiqUrl,apiKey,projectSlug. Saved via Craft's plugin settings mechanism. Settings screen accessible from CP Settings > Plugins. -
Intro screen — Replaced history list as the plugin home. Shows "Sync from ContentIQ" (primary) and "Import JSON File" (secondary) options. If API not configured, sync button links to settings instead.
-
History view — Previous import history list moved to
contentiq-importer/history, linked from the intro screen. Supports newsynctype indicator alongsidebatchandsingle. -
ContentIQ API service —
ContentIQApiServicefetches project export viaGET {url}/api/v1/projects/{slug}/exportwith Bearer auth. UsesCraft::createGuzzleClient(). -
Sync queue job —
SyncJobextendscraft\queue\BaseJob. Controller creates apendingrun record, pushes job to queue. Frontend pollssync/status?runId=Nfor completion, then redirects to sync report. CallsCraft.postActionRequest('queue/run')to kick the queue immediately. -
Per-entry sidebar widget —
EVENT_DEFINE_SIDEBAR_HTMLappends a CONTENTIQ section to every entry edit screen with Sync button and last-synced timestamp. Calls single-page API endpoint. Stored incontentiq_entry_syncstable. -
Sync report — Dedicated template showing hierarchical page tree with indentation from
depth, created/updated indicators, edit/view links, inline warnings. Summary line with page/image/warning counts. -
Hierarchy handling — All import paths (CLI batch, CP JSON import, sync queue job) support
parent_slugin the document object. UsesStructures::append()/appendToRoot()for correct sibling ordering. Maintains$slugToEntryIdmap with DB fallback for parent lookups. -
Homepage import —
is_homepage: trueroutes to thehomepageSingle section. SameheroContentBlock field as pages. Skips title overwrite and structure positioning. -
Hero ContentBlock — Both pages and homepage use
heroContentContentBlock field (handle overridehero). Importsheading,richText(subheading + body),desktopImage, andactionButtons. SetsenableHero = true. -
Hero subheading — Optional
{level, text}subheading rendered as<hN>prepended to body inrichText. -
Hero action buttons —
buttons[]array from ContentIQ imported intoactionButtonsMatrix field inside the hero ContentBlock. -
ContentIQ Cards staging block — Cards import to
contentiqCards(notcontentCards). Editors migrate to the appropriate final card block type with proper entry links. -
Cards intro field —
introContentNode[] on cards blocks imported to outerrichTextCKEditor field above the card grid. -
Cards structured body — Card
bodychanged from plain string toContentNode[]array, processed throughNodesRenderer. Supports paragraphs, lists, and embedded FAQ items. -
FAQ nodes handler —
faqNodeshandler splits thenodesarray at thefaq_itemsboundary: content before →richText, items → inner accordion entries, content after →extraRichText, CTA buttons →actionButtonsMatrix. Supports bothfields.items(primary) andnodes.faq_items(fallback) as FAQ item sources. -
USP block —
usptype maps tocontentiqUspwithuspText(richText with list support). -
Global block —
globaltype maps tocontentiqGlobalwithcontentiqNotesfor developer staging. -
Action button support —
hyperButtonhandler in MatrixBuilder converts{label, url}to Hyper field data. SetsshowLinkAsSeparateButtonwhen button present. -
NodesRenderer upgrades — Added
listnode type (withorderedboolean),faq_itemsnode type (renders as<details><summary>accordions),ctaButtonnode type (renders as<p><a href="">label</a></p>— URL left empty for editors to set). Supportsheading,paragraph,list,ordered_list,unordered_list,faq_items,ctaButton. -
Price List richer intro —
nodes(intro content) now contains headings, paragraphs, lists, and CTA buttons — all rendered viaNodesRendererintorichText. No mapping change needed;NodesRendererhandles the new node types automatically. -
Price List post-table buttons —
postNodes(CTA buttons after the table) mapped toactionButtonsMatrix via newbuttonNodeshandler inMatrixBuilder. Handler filtersctaButtonnodes from aContentNode[]array and builds the same HyperactionButtonMatrix structure used by other blocks. -
Asset filename sanitization —
Assets::prepareAssetName()applied before idempotency lookup. Prevents mismatch when Craft sanitizes filenames on save (spaces → hyphens). -
Image downloads via Guzzle — Replaced
file_get_contents()withCraft::createGuzzleClient()for SSL compatibility with dev domains. -
Slug mapping —
config/contentiq.phpslugMaptranslates Craft slugs to ContentIQ slugs for the sidebar widget sync. -
CLI default action —
ImportController::$defaultAction = 'import'socontentiq-importer/importworks without repeatingimport. -
CP nav icon — Uses Craft's built-in
copyrightsystem icon. -
Sidebar block notes — Collects
notesfrom each block during import, formats as "Block Type\nnote text", stores incontentiq_entry_syncs.notescolumn. Displayed in the sidebar widget below "Synced at". Updates in place on sync. Migrationm250419_000000_add_notes_to_entry_syncsadds the column. -
Sidebar reload link — After a successful sync, a "Reload" link appears next to the timestamp so the editor can refresh to see updated content.
-
ctaButton node type —
NodesRendererrendersctaButtonnodes as<p><a href="url">label</a></p>. -
buttonNodes handler — Maps
postNodeson price_list blocks toactionButtonsMatrix via CTA button extraction. -
Hyper linkClass — Action buttons in hero and CTA entries now include
linkClass: 'btn btn-primary'. -
Hero mobile image —
mobile_imagefield from ContentIQ hero blocks imported tomobileImageasset field on the hero ContentBlock. -
Sidebar lock toggle — CSS-only lightswitch in the CONTENTIQ sidebar. Locked entries are skipped during batch syncs (SyncJob) with a warning. Stored in
contentiq_entry_syncs.locked. Migrationm250419_000001_add_locked_to_entry_syncs. -
Sidebar clear notes — "Clear" button removes block notes via
contentiq-importer/cp/clear-notesendpoint. -
Entry title in error messages — Widget sync errors use the entry title instead of slug for readability.
-
Sync report tree fix — Report now builds a proper hierarchical tree from
parentSlugusing a recursive Twig macro instead of relying on depth + list order. Pages are grouped under their actual parents regardless of API order. -
Sync button disabled state — Dimmed at 35% opacity when locked. Re-enable checks lock state to prevent race condition if locked during an in-flight sync.
-
Hero template rewrite —
hero.twigrewritten as single file (~100 lines) reading fromentry.heroContentBlock. Deletedhero.slide.twigandhero.slide.image.twig. Removed carousel CSS. Parent image inheritance and global fallback preserved. -
New content block templates —
contentiqCards.twig,contentiqUsp.twig,contentiqGlobal.twig,priceList.twig. -
CKEditor Details/Summary plugin — Custom CKEditor 5 plugin (
modules/ckeditor-details/) viaBaseCkeditorPackageAsset. Single context-aware toolbar button: inserts a fresh<details>/<summary>block, or converts selected list items into details blocks. Registered as a Craft module. Built with Vite as ES module. Includes Enter-to-escape keyboard handling (Enter in summary jumps to content, Enter on empty last paragraph escapes the block). Uses Craft'slist-timelineicon scaled to CKEditor's 20x20 viewBox.
src/
├── jobs/
│ └── SyncJob.php # Queue job for API sync
├── models/
│ └── Settings.php # Plugin settings model
├── services/
│ └── ContentIQApiService.php # ContentIQ API client
└── templates/_cp/
├── history.twig # Import history (moved from index)
├── settings.twig # Plugin settings form
├── sync.twig # Sync screen with polling
└── sync-result.twig # Hierarchical sync report
src/
├── ContentIQImporter.php # Settings, routes, sidebar widget, icon
├── controllers/CpController.php # Intro, history, sync, widget-sync, hierarchy
├── console/controllers/ImportController.php # defaultAction, hierarchy
├── services/ImportService.php # Homepage, hero ContentBlock, hierarchy
├── services/MatrixBuilder.php # hyperButton, faqNodes handlers, internal keys
├── services/NodesRenderer.php # list, faq_items node types
├── services/ImageImportService.php # Guzzle downloads, filename sanitization
├── config/defaults.php # All block mappings updated
└── templates/_cp/index.twig # Now intro screen