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