xref: /plugin/struct/meta/CSVImporter.php (revision a0b3799e3857209811ed06eddf7c60cec2980a26)
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