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