Sub-market workbook import as a backend module tab
- Extract the complete import logic (label-scan reading, page find-or-create, slot filling, content-idempotent image attach) from ImportSubmarketWorkbooksCommand into the shared service SubmarketWorkbookImporter; the CLI command and the new controller are thin shells around importFile(), so both entry points cannot drift. CLI regression verified: identical dry-run report for the reference workbook (12 elements, page 443) - New "Sub-Markets" tab in the VITEC Import module (SubmarketImportController + Import/Submarkets.html): upload multiple filled workbooks into the SAME inbox the CLI uses (migrations/submarkets_xlsx_in/), per-file checkboxes, dry-run and apply buttons, result cards with the familiar report and a direct link into the page module; backend dry-run verified against the CLI - Submitted filenames are whitelisted against the actual directory listing (no path traversal); uploads restricted to sanitized .xlsx basenames - Routes submarkets/submarket_upload/submarket_process in Modules.php, Sub-Markets tab link in the sibling module views, ok/danger badge colors in backend-import.css - Docs section 5y (incl. operations note: apply in small batches in the module, mass runs via CLI - image downloads run inside the web request)
This commit is contained in:
1 parent
d496877a53
commit
3d5459b8d2
9 files changed
+980
-585
No files matched your search
@@ -26,6 +26,9 @@
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{f:be.uri(route: 'web_vitecimport.products')}">Products</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{f:be.uri(route: 'web_vitecimport.submarkets')}">Sub-Markets</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{f:be.uri(route: 'web_vitecimport.seo')}">SEO Research</a>
|
||||
</li>
|
||||
|
||||
@@ -23,6 +23,9 @@
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active" href="{f:be.uri(route: 'web_vitecimport.products')}">Products</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{f:be.uri(route: 'web_vitecimport.submarkets')}">Sub-Markets</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{f:be.uri(route: 'web_vitecimport.seo')}">SEO Research</a>
|
||||
</li>
|
||||
|
||||
@@ -18,6 +18,9 @@
|
||||
<a class="nav-link" href="{f:be.uri(route: 'web_vitecimport', parameters: {model: m.key})}">{m.label}</a>
|
||||
</li>
|
||||
</f:for>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{f:be.uri(route: 'web_vitecimport.submarkets')}">Sub-Markets</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active" href="{f:be.uri(route: 'web_vitecimport.seo')}">SEO Research</a>
|
||||
</li>
|
||||
|
||||
@@ -0,0 +1,141 @@
|
||||
<html xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers"
|
||||
data-namespace-typo3-fluid="true">
|
||||
|
||||
<f:layout name="Backend/Default" />
|
||||
|
||||
<f:section name="main">
|
||||
|
||||
<div class="module-docheader-bar module-docheader-bar-navigation">
|
||||
<div class="module-docheader-bar-column-left">
|
||||
<h2 class="t3js-title-inlineedit">VITEC Import — Sub-Markets</h2>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ul class="nav nav-tabs" style="margin-bottom:1rem;">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{f:be.uri(route: 'web_vitecimport')}">CSV Import</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{f:be.uri(route: 'web_vitecimport.products')}">Products</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active" href="{f:be.uri(route: 'web_vitecimport.submarkets')}">Sub-Markets</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{f:be.uri(route: 'web_vitecimport.seo')}">SEO Research</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<!-- Upload: drops Joe's filled workbooks into the shared inbox directory -->
|
||||
<div class="card" style="margin-bottom:1rem;">
|
||||
<div class="card-header"><strong>Upload filled workbooks</strong></div>
|
||||
<div class="card-body">
|
||||
<form action="{f:be.uri(route: 'web_vitecimport.submarket_upload')}" method="post" enctype="multipart/form-data" class="row row-cols-auto align-items-center g-2">
|
||||
<div class="col">
|
||||
<input type="file" name="workbooks[]" accept=".xlsx" multiple="multiple" class="form-control" required="required" />
|
||||
</div>
|
||||
<div class="col">
|
||||
<button type="submit" class="btn btn-primary">Upload</button>
|
||||
</div>
|
||||
<div class="col form-text">
|
||||
Multiple files allowed. A file with the same name replaces the stored one ({inbox}/).
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Inbox + dry-run / apply -->
|
||||
<div class="card" style="margin-bottom:1rem;">
|
||||
<div class="card-header"><strong>Workbooks in the inbox</strong></div>
|
||||
<div class="card-body">
|
||||
<f:if condition="{workbooks}">
|
||||
<f:then>
|
||||
<form action="{f:be.uri(route: 'web_vitecimport.submarket_process')}" method="post">
|
||||
<table class="table table-striped table-sm" style="max-width:900px;">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:30px;"></th>
|
||||
<th>File</th>
|
||||
<th>Size</th>
|
||||
<th>Uploaded / changed</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<f:for each="{workbooks}" as="w">
|
||||
<tr>
|
||||
<td><input type="checkbox" name="files[]" value="{w.name}" checked="checked" /></td>
|
||||
<td>{w.name}</td>
|
||||
<td>{w.size}</td>
|
||||
<td>{w.modified}</td>
|
||||
</tr>
|
||||
</f:for>
|
||||
</tbody>
|
||||
</table>
|
||||
<button type="submit" name="op" value="dry" class="btn btn-default">Dry run (no changes)</button>
|
||||
<button type="submit" name="op" value="apply" class="btn btn-warning">Apply — write pages</button>
|
||||
<span class="form-text" style="display:inline-block;margin-left:.75rem;">
|
||||
Always dry-run first. Apply updates existing elements in place, creates missing ones, never deletes.
|
||||
New pages are created HIDDEN for editorial review.
|
||||
</span>
|
||||
</form>
|
||||
</f:then>
|
||||
<f:else>
|
||||
<p>No workbooks yet — upload Joe's filled .xlsx files above.</p>
|
||||
</f:else>
|
||||
</f:if>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Results -->
|
||||
<f:if condition="{hasResults}">
|
||||
<f:for each="{results}" as="r">
|
||||
<div class="card" style="margin-bottom:1rem;">
|
||||
<div class="card-header">
|
||||
<strong>{r.file}</strong>
|
||||
<f:if condition="{r.error}">
|
||||
<f:then>
|
||||
<span class="vitec-badge vitec-badge-danger">ERROR</span>
|
||||
</f:then>
|
||||
<f:else>
|
||||
— {r.group} / {r.submarket}
|
||||
<f:if condition="{r.applied}">
|
||||
<f:then><span class="vitec-badge vitec-badge-ok">written</span></f:then>
|
||||
<f:else><span class="vitec-badge vitec-badge-info">dry run</span></f:else>
|
||||
</f:if>
|
||||
</f:else>
|
||||
</f:if>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<f:if condition="{r.error}">
|
||||
<f:then>
|
||||
<p class="text-danger">{r.error}</p>
|
||||
</f:then>
|
||||
<f:else>
|
||||
<p>
|
||||
Page:
|
||||
<f:if condition="{r.pageUid}">
|
||||
<f:then>
|
||||
<a href="{f:be.uri(route: 'web_layout', parameters: {id: r.pageUid})}">#{r.pageUid} — open in page module</a>
|
||||
</f:then>
|
||||
<f:else>
|
||||
NEW (would be created hidden)
|
||||
</f:else>
|
||||
</f:if>
|
||||
· {r.elements} element(s) touched
|
||||
</p>
|
||||
<f:if condition="{r.report}">
|
||||
<ul style="margin-bottom:0;">
|
||||
<f:for each="{r.report}" as="line">
|
||||
<li>{line}</li>
|
||||
</f:for>
|
||||
</ul>
|
||||
</f:if>
|
||||
</f:else>
|
||||
</f:if>
|
||||
</div>
|
||||
</div>
|
||||
</f:for>
|
||||
</f:if>
|
||||
|
||||
</f:section>
|
||||
</html>
|
||||
@@ -95,6 +95,14 @@
|
||||
background-color: #313131;
|
||||
color: #ffffff;
|
||||
}
|
||||
.vitec-administration .vitec-badge-ok {
|
||||
background-color: #2f9e44;
|
||||
color: #ffffff;
|
||||
}
|
||||
.vitec-administration .vitec-badge-danger {
|
||||
background-color: #c0392b;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
/* --- form focus (frontend uses a 3px orange outline) ---------------- */
|
||||
|
||||
|
||||
Reference in new issue
Block a user