1<?php 2 3namespace dokuwiki\plugin\struct\meta; 4 5/** 6 * Class CSVImporter 7 * 8 * Imports CSV data into a lookup schema 9 * 10 * @package dokuwiki\plugin\struct\meta 11 */ 12class CSVImporter { 13 14 /** @var Schema */ 15 protected $schema; 16 17 /** @var resource */ 18 protected $fh; 19 20 /** @var \helper_plugin_sqlite */ 21 protected $sqlite; 22 23 /** @var Column[] The single values to store index => col */ 24 protected $columns = array(); 25 26 /** @var int current line number */ 27 protected $line = 0; 28 29 /** 30 * CSVImporter constructor. 31 * 32 * @throws StructException 33 * @param string $table 34 * @param string $file 35 */ 36 public function __construct($table, $file) { 37 $this->fh = fopen($file, 'r'); 38 if(!$this->fh) throw new StructException('Failed to open CSV file for reading'); 39 40 $this->schema = new Schema($table); 41 if(!$this->schema->getId()) throw new StructException('Schema does not exist'); 42 43 if(!$this->schema->isLookup()) throw new StructException('CSV import is only valid for Lookup Schemas'); 44 45 /** @var \helper_plugin_struct_db $db */ 46 $db = plugin_load('helper', 'struct_db'); 47 $this->sqlite = $db->getDB(true); 48 49 // Do the import 50 $this->readHeaders(); 51 $this->importCSV(); 52 } 53 54 /** 55 * Read the CSV headers and match it with the Schema columns 56 */ 57 protected function readHeaders() { 58 $header = fgetcsv($this->fh); 59 if(!$header) throw new StructException('Failed to read CSV'); 60 $this->line++; 61 62 foreach($header as $i => $head) { 63 $col = $this->schema->findColumn($head); 64 if(!$col) continue; 65 if(!$col->isEnabled()) continue; 66 $this->columns[$i] = $col; 67 } 68 69 if(!$this->columns) { 70 throw new StructException('None of the CSV headers matched any of the schema\'s fields'); 71 } 72 } 73 74 /** 75 * Creates the insert string for the single value table 76 * 77 * @return string 78 */ 79 protected function getSQLforAllValues() { 80 $colnames = array(); 81 $placeholds = array(); 82 foreach($this->columns as $i => $col) { 83 $colnames[] = 'col' . $col->getColref(); 84 $placeholds[] = '?'; 85 } 86 $colnames = join(', ', $colnames); 87 $placeholds = join(', ', $placeholds); 88 $table = $this->schema->getTable(); 89 90 return "INSERT INTO data_$table ($colnames) VALUES ($placeholds)"; 91 } 92 93 /** 94 * Creates the insert string for the multi value table 95 * 96 * @return string 97 */ 98 protected function getSQLforMultiValue() { 99 $table = $this->schema->getTable(); 100 /** @noinspection SqlResolve */ 101 return "INSERT INTO multi_$table (pid, colref, row, value) VALUES (?,?,?,?)"; 102 } 103 104 /** 105 * Walks through the CSV and imports 106 */ 107 protected function importCSV() { 108 109 $single = $this->getSQLforAllValues(); 110 $multi = $this->getSQLforMultiValue(); 111 112 $this->sqlite->query('BEGIN TRANSACTION'); 113 while(($data = fgetcsv($this->fh)) !== false) { 114 $this->line++; 115 $this->importLine($data, $single, $multi); 116 } 117 $this->sqlite->query('COMMIT TRANSACTION'); 118 } 119 120 /** 121 * Imports one line into the schema 122 * 123 * @param string[] $line the parsed CSV line 124 * @param string $single SQL for single table 125 * @param string $multi SQL for multi table 126 */ 127 protected function importLine($line, $single, $multi) { 128 // prepare values for single value table 129 $values = array(); 130 foreach($this->columns as $i => $column) { 131 if(!isset($line[$i])) throw new StructException('Missing field at CSV line %d', $this->line); 132 133 if($column->isMulti()) { 134 // multi values get split on comma 135 $line[$i] = array_map('trim', explode(',', $line[$i])); 136 $values[] = $line[$i][0]; 137 } else { 138 $values[] = $line[$i]; 139 } 140 } 141 142 // insert into single value table (and create pid) 143 $this->sqlite->query($single, $values); 144 $res = $this->sqlite->query('SELECT last_insert_rowid()'); 145 $pid = $this->sqlite->res2single($res); 146 $this->sqlite->res_close($res); 147 148 // insert all the multi values 149 foreach($this->columns as $i => $column) { 150 if(!$column->isMulti()) continue; 151 foreach($line[$i] as $row => $value) { 152 $this->sqlite->query($multi, array($pid, $column->getColref(), $row + 1, $value)); 153 } 154 } 155 } 156 157} 158