[TikiWiki-commits] [Git][tikiwiki/tiki][27.x] [BP][NEW] add tab delimiter to Tabular Import

"Jean-Marc Libs \(@jyhem\) via TikiWiki-cvs" <[email protected]> Fri, 17 Jul 2026 17:23:57 +0000
Newsgroups gmane.comp.cms.tiki.cvs
Message-ID <6a5a652d1d538_3819d0345768@gitlab-sidekiq-low-urgency-cpu-bound-v2-596569fc56-lfcj4.mail>

Jean-Marc Libs pushed to branch 27.x at Tiki Wiki CMS Groupware / Tiki


Commits:
9c8cb945 by Jonny Bradley at 2026-07-17T17:17:42+00:00
[BP][NEW] add tab delimiter to Tabular Import
---
* [FIX] stupid typo, fixed in master as part of monster commit !4708 so was almost impossible to find - but did eventually!
Thanks @jyhem

* [NEW] add tab delimiter to Tabular Import
---
* Fix pipeline: remove superfluous whitespaces

* Update import_items csv form

* Take in account fields that may contains tabs or can be empty on tsv import

* Tabular import: refactor and improve .tsv import, handling data that may have ',' or '"' inside

* Ask for separator when "Import" is clicked in tabular

* [NEW] Importing data from CSV files separated by commas, semicolons, or tabs

* [NEW] add tab delimiter to Tabular Import

See merge request tikiwiki/tiki!5575

(cherry picked from commit 88ccb0003f50ef233ab4e0018f1346f39bb17048)

See merge request tikiwiki/tiki!10660

- - - - -


7 changed files:

- lib/core/Services/Tracker/Controller.php
- lib/core/Services/Tracker/TabularController.php
- lib/core/Services/Tracker/Utilities.php
- lib/core/Tracker/Tabular/Schema.php
- templates/tabular/create_tracker.tpl
- templates/tabular/import_csv.tpl
- templates/tracker/import_items.tpl


Changes:

=====================================
lib/core/Services/Tracker/Controller.php
=====================================
@@ -2706,6 +2706,8 @@ class Services_Tracker_Controller
     public function action_import_items($input)
     {
         $trackerId = $input->trackerId->int();
+        $separator = $input->separator->text();
+        $fileIsTsv = ! ($separator == "," || $separator == ';');
 
         $perms = Perms::get('tracker', $trackerId);
         if (! $perms->admin_trackers) {
@@ -2723,6 +2725,11 @@ class Services_Tracker_Controller
                 throw new Services_Exception(tr('File upload failed.'), 400);
             }
 
+            if ($fileIsTsv) {
+                Services_Tracker_Utilities::parseTsvContentToCsv('importfile');
+                $separator = ',';
+            }
+
             if (! $fp = @ fopen($_FILES['importfile']['tmp_name'], "rb")) {
                 throw new Services_Exception(tr('Uploaded file could not be read.'), 500);
             }
@@ -2734,7 +2741,7 @@ class Services_Tracker_Controller
                 ($input->add_items->int() !== 1), // checkbox is "Create as new items" - param is replace_rows
                 $input->dateFormat->text(),
                 $input->encoding->text(),
-                $input->separator->text(),
+                $separator,
                 $input->updateLastModif->int(),
                 $input->convertItemLinkValues->int()
             );


=====================================
lib/core/Services/Tracker/TabularController.php
=====================================
@@ -543,7 +543,8 @@ class Services_Tracker_TabularController
     {
         $lib = TikiLib::lib('tabular');
         $info = $lib->getInfo($input->tabularId->int());
-        $trackerId = $info['trackerId'];
+        $separator = $input->separator->text();
+        $fileIsTsv = ! ($separator == "," || $separator == ';');
 
         Services_Exception_Denied::checkObject('tiki_p_tabular_import', 'tabular', $info['tabularId']);
 
@@ -553,7 +554,11 @@ class Services_Tracker_TabularController
         $done = false;
 
         if ($_SERVER['REQUEST_METHOD'] == 'POST' && is_uploaded_file($_FILES['file']['tmp_name'])) {
-            $source = $schema->getSource($_FILES['file']['tmp_name']);
+            if ($fileIsTsv) {
+                Services_Tracker_Utilities::parseTsvContentToCsv('file');
+                $separator = ',';
+            }
+            $source = $schema->getSource($_FILES['file']['tmp_name'], $separator);
             $writer = new TrackerWriter();
 
             return TikiLib::lib('tiki')->allocate_extra(
@@ -919,7 +924,14 @@ class Services_Tracker_TabularController
 
             if (is_uploaded_file($_FILES['file']['tmp_name'])) {
                 try {
-                    $delimiter = $input->delimiter->text() == 'comma' ? ',' : ';';
+                    $delimiter = ',';
+
+                    if ($input->delimiter->text() == 'semicolon') {
+                        $delimiter = ';';
+                    } elseif ($input->delimiter->text() == 'tab') {
+                        $delimiter = "\t";
+                    }
+
                     $source = new CsvSource($schema, $_FILES['file']['tmp_name'], $delimiter);
                     $writer = new TrackerWriter();
                     $done = $writer->write($source);


=====================================
lib/core/Services/Tracker/Utilities.php
=====================================
@@ -722,4 +722,41 @@ EXPORT;
         $currency = new Math_Formula_Currency($data['amount'], $data['currency'], $rates);
         return $currency->convertTo($defaultCurrency)->getAmount();
     }
+
+    public static function parseTsvContentToCsv($filename)
+    {
+        $fileContent = file_get_contents($_FILES[$filename]['tmp_name']);
+        $rows = explode("\n", $fileContent);
+        $csvRows = [];
+
+        foreach ($rows as $row) {
+            if (trim($row) === '') {
+                continue;
+            }
+            // since field may have comma or quotes or tab in it
+            $fields = str_getcsv($row, "\t");
+            if (substr($row, -1) === "\t") {
+                $fields[] = '';
+            }
+            $pFields = []; // escape processed fields
+
+            foreach ($fields as $field) {
+                $field = $field ?? '';
+                $hasCommaOrQuoteOrTab = (
+                    strpos($field, ',') !== false ||
+                    strpos($field, '"') !== false ||
+                    strpos($field, "\t") !== false
+                );
+
+                if ($hasCommaOrQuoteOrTab) {
+                    $pFields[] = '"' . str_replace('"', '""', $field) . '"';
+                } else {
+                    $pFields[] = $field;
+                }
+            }
+            $csvRows[] = implode(',', $pFields);
+        }
+        $_FILES[$filename]['tmp_name'] = tempnam(sys_get_temp_dir(), 'modified_');
+        file_put_contents($_FILES[$filename]['tmp_name'], implode("\n", $csvRows));
+    }
 }


=====================================
lib/core/Tracker/Tabular/Schema.php
=====================================
@@ -392,7 +392,7 @@ class Schema
         return $writer;
     }
 
-    public function getSource($fileName)
+    public function getSource($fileName, $separator = ",")
     {
         switch ($this->getFormat()) {
             case 'json':
@@ -401,7 +401,7 @@ class Schema
             case 'ical':
                 return new Source\IcalSource($this, $fileName, $this->getEncoding());
             default:
-                return new Source\CsvSource($this, $fileName, ',', $this->getEncoding());
+                return new Source\CsvSource($this, $fileName, $separator, $this->getEncoding());
         }
     }
 


=====================================
templates/tabular/create_tracker.tpl
=====================================
@@ -33,6 +33,7 @@
                 <select class="form-select file-delimiter">
                     <option value="comma" selected>{tr}Comma (,){/tr}</option>
                     <option value="semicolon">{tr}Semicolon (;){/tr}</option>
+                    <option value="tab">{tr}Tab (\t){/tr}</option>
                 </select>
                 <input type="hidden" id="delimiter" name="delimiter" value="comma">
             </div>
@@ -40,7 +41,7 @@
         <div class="tiki-form-group row file-container">
             <label class="col-form-label col-sm-3" for="file">{tr}File{/tr}</label>
             <div class="col-sm-9">
-                <input type="file" name="file" id="file" accept="text/csv" class="form-control" required>
+                <input type="file" name="file" id="file" accept=".csv, .tsv, text/csv, text/tsv" class="form-control" required>
                 <div id="file-size-error" class="alert alert-danger mt-3" style="display:none">
                     {tr _0=$config.upload_max_filesize}<strong>Error:</strong> Selected file has <span id="file-size"></span> bytes. The max file size upload is %0 bytes.{/tr}
                 </div>
@@ -192,7 +193,15 @@
 
         $('.file-delimiter').on('change', function(e) {
             var currentValue = $(this).val();
-            delimiter = ((currentValue == 'comma') ? ',' : ';');
+
+            if (currentValue == 'comma') {
+                delimiter = ',';
+            } else if (currentValue == 'semicolon') {
+                delimiter = ';';
+            } else if (currentValue == 'tab') {
+                delimiter = '\t';
+            }
+
             delimiterElement.val(currentValue);
             fileSelect.trigger("change");
         });
@@ -221,9 +230,12 @@
                 reader.onload = function (e) {
                     if (delimiter == ';') {
                         var rows = Plotly.d3.dsv(';').parse(e.target.result);
+                    } else if (delimiter == '\t') {
+                        var rows = Plotly.d3.dsv('\t').parse(e.target.result);
                     } else {
                         var rows = Plotly.d3.csv.parse(e.target.result);
                     }
+                    
                     var columns = Object.keys(rows[0]);
 
                     for(i = 0; i < columns.length; i++) {


=====================================
templates/tabular/import_csv.tpl
=====================================
@@ -47,7 +47,15 @@
             {elseif $format eq 'ical'}
                 {createFileInput format='ical' label='iCal File' fileInputId='icalInputFile' fileAccept='text/calendar'}
             {else}
-                {createFileInput format='csv' label='CSV File' fileInputId='inputFile' fileAccept='text/csv'}
+                {createFileInput format='csv' label='CSV File' fileInputId='inputFile' fileAccept='text/csv, .tsv'}
+                <div class="form-group">
+                    <label for="separator">{tr}Select separator:{/tr}</label>
+                    <select name="separator" class="form-select" aria-label="CSV File delimiter">
+                        <option value=",">{tr}Comma{/tr}(,)</option>
+                        <option value=";">{tr}Semicolon{/tr}(;)</option>
+                        <option value="\t">{tr}Tab{/tr}(\t)</option>
+                    </select>
+                </div>
             {/if}     
             <div class="submit">
                 <input class="btn btn-primary" type="submit" value="{tr}Import{/tr}">


=====================================
templates/tracker/import_items.tpl
=====================================
@@ -51,8 +51,12 @@
         </select>
     </div>
     <div class="mb-3 row mx-0">
-        <label for="separator">{tr}Separator{/tr}</label>
-        <input type="text" name="separator" value="," size="2" class="form-control">
+        <label for="separator">{tr}Select separator:{/tr}</label>
+        <select name="separator" class="form-select file-delimiter">
+            <option value="," selected>{tr}Comma{/tr}(,)</option>
+            <option value=";">{tr}Semicolon{/tr}(;)</option>
+            <option value="\t">{tr}Tab{/tr}(\t)</option>
+        </select>
     </div>
     <div class="form-check">
         <input type="checkbox" class="form-check-input" name="add_items" id="add_items" value="1">



View it on GitLab: https://gitlab.com/tikiwiki/tiki/-/commit/9c8cb945252a0a2716b83ed706217e041eca984c

-- 
View it on GitLab: https://gitlab.com/tikiwiki/tiki/-/commit/9c8cb945252a0a2716b83ed706217e041eca984c
You're receiving this email because of your account on gitlab.com. Manage all notifications: https://gitlab.com/-/profile/notifications | Help: https://gitlab.com/help

_______________________________________________
TikiWiki-cvs mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/tikiwiki-cvs