Merge branch 'bug_2832' into 3.12-master
[koha.git] / C4 / ImportExportFramework.pm
1 package C4::ImportExportFramework;
2
3 # Copyright 2010-2011 MASmedios.com y Ministerio de Cultura
4 #
5 # This file is part of Koha.
6 #
7 # Koha is free software; you can redistribute it and/or modify it under the
8 # terms of the GNU General Public License as published by the Free Software
9 # Foundation; either version 2 of the License, or (at your option) any later
10 # version.
11 #
12 # Koha is distributed in the hope that it will be useful, but WITHOUT ANY
13 # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
14 # A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License along
17 # with Koha; if not, write to the Free Software Foundation, Inc.,
18 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19
20 use strict;
21 use warnings;
22 use XML::LibXML;
23 use XML::LibXML::XPathContext;
24 use Digest::MD5 qw(md5_base64);
25 use POSIX qw(strftime);
26
27 use C4::Context;
28 use C4::Debug;
29
30
31 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
32
33 BEGIN {
34     $VERSION = 3.07.00.049;    # set version for version checking
35     require Exporter;
36     @ISA    = qw(Exporter);
37     @EXPORT = qw(
38         &ExportFramework
39         &ImportFramework
40         &createODS
41     );
42 }
43
44
45 use constant XMLSTR => '<?xml version="1.0" encoding="UTF-8"?>
46 <?mso-application progid="Excel.Sheet"?>
47 <Workbook
48   xmlns:x="urn:schemas-microsoft-com:office:excel"
49   xmlns="urn:schemas-microsoft-com:office:spreadsheet"
50   xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet">
51
52 <Styles>
53  <Style ss:ID="Default" ss:Name="Normal">
54   <Alignment ss:Vertical="Bottom"/>
55   <Borders/>
56   <Font/>
57   <Interior/>
58   <NumberFormat/>
59   <Protection/>
60  </Style>
61  <Style ss:ID="s27">
62   <Font x:Family="Swiss" ss:Color="#0000FF" ss:Bold="1"/>
63  </Style>
64  <Style ss:ID="s21">
65   <NumberFormat ss:Format="yyyy\-mm\-dd"/>
66  </Style>
67  <Style ss:ID="s22">
68   <NumberFormat ss:Format="yyyy\-mm\-dd\ hh:mm:ss"/>
69  </Style>
70  <Style ss:ID="s23">
71   <NumberFormat ss:Format="hh:mm:ss"/>
72  </Style>
73 </Styles>
74
75 </Workbook>
76 ';
77
78
79 use constant ODSSTR => '<?xml version="1.0" encoding="UTF-8"?>
80 <office:document-content xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0" xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0" xmlns:draw="urn:oasis:names:tc:opendocument:xmlns:drawing:1.0" xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:meta="urn:oasis:names:tc:opendocument:xmlns:meta:1.0" xmlns:number="urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0" xmlns:svg="urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0" xmlns:chart="urn:oasis:names:tc:opendocument:xmlns:chart:1.0" xmlns:dr3d="urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0" xmlns:math="http://www.w3.org/1998/Math/MathML" xmlns:form="urn:oasis:names:tc:opendocument:xmlns:form:1.0" xmlns:script="urn:oasis:names:tc:opendocument:xmlns:script:1.0" xmlns:ooo="http://openoffice.org/2004/office" xmlns:ooow="http://openoffice.org/2004/writer" xmlns:oooc="http://openoffice.org/2004/calc" xmlns:dom="http://www.w3.org/2001/xml-events" xmlns:xforms="http://www.w3.org/2002/xforms" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" office:version="1.0">
81 <office:scripts/>
82 <office:font-face-decls/>
83 <office:automatic-styles/>
84 </office:document-content>';
85
86
87 use constant ODS_STYLES_STR => '<?xml version="1.0" encoding="UTF-8"?>
88 <office:document-styles xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0" xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0" xmlns:draw="urn:oasis:names:tc:opendocument:xmlns:drawing:1.0" xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:meta="urn:oasis:names:tc:opendocument:xmlns:meta:1.0" xmlns:number="urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0" xmlns:svg="urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0" xmlns:chart="urn:oasis:names:tc:opendocument:xmlns:chart:1.0" xmlns:dr3d="urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0" xmlns:math="http://www.w3.org/1998/Math/MathML" xmlns:form="urn:oasis:names:tc:opendocument:xmlns:form:1.0" xmlns:script="urn:oasis:names:tc:opendocument:xmlns:script:1.0" xmlns:ooo="http://openoffice.org/2004/office" xmlns:ooow="http://openoffice.org/2004/writer" xmlns:oooc="http://openoffice.org/2004/calc" xmlns:dom="http://www.w3.org/2001/xml-events" office:version="1.0">
89 <office:font-face-decls></office:font-face-decls>
90 <office:styles></office:styles>
91 <office:automatic-styles></office:automatic-styles>
92 <office:master-styles></office:master-styles>
93 </office:document-styles>';
94
95
96 use constant ODS_SETTINGS_STR => '<?xml version="1.0" encoding="UTF-8"?>
97 <office:document-settings xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:config="urn:oasis:names:tc:opendocument:xmlns:config:1.0" xmlns:ooo="http://openoffice.org/2004/office" office:version="1.0"><office:settings>
98 <config:config-item-set config:name="ooo:view-settings">
99 <config:config-item config:name="VisibleAreaTop" config:type="int">0</config:config-item>
100 <config:config-item config:name="VisibleAreaLeft" config:type="int">0</config:config-item>
101 <config:config-item config:name="VisibleAreaWidth" config:type="int">2000</config:config-item>
102 <config:config-item config:name="VisibleAreaHeight" config:type="int">900</config:config-item>
103 <config:config-item-map-indexed config:name="Views"><config:config-item-map-entry>
104 <config:config-item config:name="ViewId" config:type="string">View1</config:config-item>
105 <config:config-item-map-named config:name="Tables">
106 <config:config-item-map-entry config:name="Sheet1"><config:config-item config:name="CursorPositionX" config:type="int">0</config:config-item><config:config-item config:name="CursorPositionY" config:type="int">1</config:config-item><config:config-item config:name="HorizontalSplitMode" config:type="short">0</config:config-item><config:config-item config:name="VerticalSplitMode" config:type="short">0</config:config-item><config:config-item config:name="HorizontalSplitPosition" config:type="int">0</config:config-item><config:config-item config:name="VerticalSplitPosition" config:type="int">0</config:config-item><config:config-item config:name="ActiveSplitRange" config:type="short">2</config:config-item><config:config-item config:name="PositionLeft" config:type="int">0</config:config-item><config:config-item config:name="PositionRight" config:type="int">0</config:config-item><config:config-item config:name="PositionTop" config:type="int">0</config:config-item><config:config-item config:name="PositionBottom" config:type="int">0</config:config-item>
107 </config:config-item-map-entry>
108 </config:config-item-map-named>
109 <config:config-item config:name="ActiveTable" config:type="string">Sheet1</config:config-item>
110 <config:config-item config:name="HorizontalScrollbarWidth" config:type="int">270</config:config-item>
111 <config:config-item config:name="ZoomType" config:type="short">0</config:config-item>
112 <config:config-item config:name="ZoomValue" config:type="int">100</config:config-item>
113 <config:config-item config:name="PageViewZoomValue" config:type="int">50</config:config-item>
114 <config:config-item config:name="ShowPageBreakPreview" config:type="boolean">false</config:config-item>
115 <config:config-item config:name="ShowZeroValues" config:type="boolean">true</config:config-item>
116 <config:config-item config:name="ShowNotes" config:type="boolean">true</config:config-item>
117 <config:config-item config:name="ShowGrid" config:type="boolean">true</config:config-item>
118 <config:config-item config:name="GridColor" config:type="long">12632256</config:config-item>
119 <config:config-item config:name="ShowPageBreaks" config:type="boolean">true</config:config-item>
120 <config:config-item config:name="HasColumnRowHeaders" config:type="boolean">true</config:config-item>
121 <config:config-item config:name="HasSheetTabs" config:type="boolean">true</config:config-item>
122 <config:config-item config:name="IsOutlineSymbolsSet" config:type="boolean">true</config:config-item>
123 <config:config-item config:name="IsSnapToRaster" config:type="boolean">false</config:config-item>
124 <config:config-item config:name="RasterIsVisible" config:type="boolean">false</config:config-item>
125 <config:config-item config:name="IsRasterAxisSynchronized" config:type="boolean">true</config:config-item></config:config-item-map-entry></config:config-item-map-indexed>
126 </config:config-item-set>
127 <config:config-item-set config:name="ooo:configuration-settings">
128 <config:config-item config:name="ShowZeroValues" config:type="boolean">true</config:config-item>
129 <config:config-item config:name="ShowNotes" config:type="boolean">true</config:config-item>
130 <config:config-item config:name="ShowGrid" config:type="boolean">true</config:config-item>
131 <config:config-item config:name="GridColor" config:type="long">12632256</config:config-item>
132 <config:config-item config:name="ShowPageBreaks" config:type="boolean">true</config:config-item>
133 <config:config-item config:name="LinkUpdateMode" config:type="short">3</config:config-item>
134 <config:config-item config:name="HasColumnRowHeaders" config:type="boolean">true</config:config-item>
135 <config:config-item config:name="HasSheetTabs" config:type="boolean">true</config:config-item>
136 <config:config-item config:name="IsOutlineSymbolsSet" config:type="boolean">true</config:config-item>
137 <config:config-item config:name="IsSnapToRaster" config:type="boolean">false</config:config-item>
138 <config:config-item config:name="RasterIsVisible" config:type="boolean">false</config:config-item>
139 <config:config-item config:name="IsRasterAxisSynchronized" config:type="boolean">true</config:config-item>
140 <config:config-item config:name="AutoCalculate" config:type="boolean">true</config:config-item>
141 <config:config-item config:name="PrinterName" config:type="string">Generic Printer</config:config-item>
142 <config:config-item config:name="ApplyUserData" config:type="boolean">true</config:config-item>
143 <config:config-item config:name="CharacterCompressionType" config:type="short">0</config:config-item>
144 <config:config-item config:name="SaveVersionOnClose" config:type="boolean">false</config:config-item>
145 <config:config-item config:name="UpdateFromTemplate" config:type="boolean">false</config:config-item>
146 <config:config-item config:name="AllowPrintJobCancel" config:type="boolean">true</config:config-item>
147 <config:config-item config:name="LoadReadonly" config:type="boolean">false</config:config-item>
148 </config:config-item-set>
149 </office:settings></office:document-settings>';
150
151
152 use constant ODS_MANIFEST_STR => '<?xml version="1.0" encoding="UTF-8"?>
153 <manifest:manifest xmlns:manifest="urn:oasis:names:tc:opendocument:xmlns:manifest:1.0">
154  <manifest:file-entry manifest:media-type="application/vnd.oasis.opendocument.spreadsheet" manifest:full-path="/"/>
155  <manifest:file-entry manifest:media-type="" manifest:full-path="Configurations2/statusbar/"/>
156  <manifest:file-entry manifest:media-type="" manifest:full-path="Configurations2/accelerator/"/>
157  <manifest:file-entry manifest:media-type="" manifest:full-path="Configurations2/floater/"/>
158  <manifest:file-entry manifest:media-type="" manifest:full-path="Configurations2/popupmenu/"/>
159  <manifest:file-entry manifest:media-type="" manifest:full-path="Configurations2/progressbar/"/>
160  <manifest:file-entry manifest:media-type="" manifest:full-path="Configurations2/menubar/"/>
161  <manifest:file-entry manifest:media-type="" manifest:full-path="Configurations2/toolbar/"/>
162  <manifest:file-entry manifest:media-type="" manifest:full-path="Configurations2/images/Bitmaps/"/>
163  <manifest:file-entry manifest:media-type="" manifest:full-path="Configurations2/images/"/>
164  <manifest:file-entry manifest:media-type="application/vnd.sun.xml.ui.configuration" manifest:full-path="Configurations2/"/>
165  <manifest:file-entry manifest:media-type="text/xml" manifest:full-path="content.xml"/>
166  <manifest:file-entry manifest:media-type="text/xml" manifest:full-path="styles.xml"/>
167  <manifest:file-entry manifest:media-type="text/xml" manifest:full-path="meta.xml"/>
168  <manifest:file-entry manifest:media-type="" manifest:full-path="Thumbnails/"/>
169  <manifest:file-entry manifest:media-type="text/xml" manifest:full-path="settings.xml"/>
170 </manifest:manifest>';
171
172
173 =head1 NAME
174
175 C4::ImportExportFramework - Import/Export Framework to Excel-xml/ODS Module Functions
176
177 =head1 SYNOPSIS
178
179   use C4::ImportExportFramework;
180
181 =head1 DESCRIPTION
182
183 Module to Import/Export Framework to Excel-xml/ODS on intranet administration - MARC Frameworks section
184
185 Module to Import/Export Framework to Excel-xml/ODS on intranet administration - MARC Frameworks section
186 exporting the tables marc_tag_structure, marc_subfield_structure to excel-xml/ods or viceversa
187
188 Functions for handling import/export.
189
190
191 =head1 SUBROUTINES
192
193
194
195 =head2 ExportFramework
196
197 Export all the information of a Framework to an excel "xml" file or OpenDocument SpreadSheet "ods" file.
198
199 return :
200 succes
201
202 =cut
203
204 sub ExportFramework
205 {
206     my ($frameworkcode, $xmlStrRef, $mode) = @_;
207
208     my $dbh = C4::Context->dbh;
209     if ($dbh) {
210         my $dom;
211         my $root;
212         my $elementSS;
213         if ($mode eq 'ods' || $mode eq 'excel') {
214             eval {
215                 my $parser = XML::LibXML->new();
216                 $dom = $parser->parse_string(($mode && $mode eq 'ods')?ODSSTR:XMLSTR);
217                 if ($dom) {
218                     $root = $dom->documentElement();
219                     if ($mode && $mode eq 'ods') {
220                         my $elementBody = $dom->createElement('office:body');
221                         $root->appendChild($elementBody);
222                         $elementSS = $dom->createElement('office:spreadsheet');
223                         $elementBody->appendChild($elementSS);
224                     }
225                 }
226             };
227             if ($@) {
228                 $debug and warn "Error ExportFramework $@\n";
229                 return 0;
230             }
231         }
232
233         if (_export_table('marc_tag_structure', $dbh, ($mode eq 'csv' || $mode eq 'sql')?$xmlStrRef:$dom, ($mode eq 'ods')?$elementSS:$root, $frameworkcode, $mode)) {
234             if (_export_table('marc_subfield_structure', $dbh, ($mode eq 'csv' || $mode eq 'sql')?$xmlStrRef:$dom, ($mode eq 'ods')?$elementSS:$root, $frameworkcode, $mode)) {
235                 $$xmlStrRef = $dom->toString(1) if ($mode eq 'ods' || $mode eq 'excel');
236                 return 1;
237             }
238         }
239     }
240     return 0;
241 }#ExportFramework
242
243
244
245
246 # Export all the data from a mysql table to an spreadsheet.
247 sub _export_table
248 {
249     my ($table, $dbh, $dom, $root, $frameworkcode, $mode) = @_;
250     if ($mode eq 'csv') {
251         _export_table_csv($table, $dbh, $dom, $root, $frameworkcode);
252     } elsif ($mode eq 'sql') {
253         _export_table_sql($table, $dbh, $dom, $root, $frameworkcode);
254     } elsif ($mode eq 'ods') {
255         _export_table_ods($table, $dbh, $dom, $root, $frameworkcode);
256     } else {
257         _export_table_excel($table, $dbh, $dom, $root, $frameworkcode);
258     }
259 }
260
261
262 # Export the mysql table to an sql file
263 sub _export_table_sql
264 {
265     my ($table, $dbh, $strSQL, $root, $frameworkcode) = @_;
266
267     eval {
268         # First row with the name of the columns
269         my $query = 'SHOW COLUMNS FROM ' . $table;
270         my $sth = $dbh->prepare($query);
271         $sth->execute();
272         my @fields = ();
273         while (my $hashRef = $sth->fetchrow_hashref) {
274             push @fields, $hashRef->{Field};
275         }
276         my $fields = join(',', @fields);
277         $$strSQL .= 'DELETE FROM ' . $table . ' WHERE frameworkcode=' . $dbh->quote($frameworkcode) . ';';
278         $$strSQL .= chr(10);
279         # Populate rows with the data from mysql
280         $query = 'SELECT * FROM ' . $table . ' WHERE frameworkcode=?';
281         $sth = $dbh->prepare($query);
282         $sth->execute($frameworkcode);
283         while (my $hashRef = $sth->fetchrow_hashref) {
284             $$strSQL .= 'INSERT INTO ' . $table . ' (' . $fields . ') VALUES (';
285             for (@fields) {
286                 $$strSQL .= $dbh->quote($hashRef->{$_}) . ',';
287             }
288             chop $$strSQL;
289             $$strSQL .= ');' . chr(10);
290         }
291         $$strSQL .= chr(10) . chr(10);
292     };
293     if ($@) {
294         $debug and warn "Error _export_table_sql $@\n";
295         return 0;
296     }
297     return 1;
298 }#_export_table_sql
299
300
301 # Export the mysql table to an csv file
302 sub _export_table_csv
303 {
304     my ($table, $dbh, $strCSV, $root, $frameworkcode) = @_;
305
306     eval {
307         # First row with the name of the columns
308         my $query = 'SHOW COLUMNS FROM ' . $table;
309         my $sth = $dbh->prepare($query);
310         $sth->execute();
311         my @fields = ();
312         while (my $hashRef = $sth->fetchrow_hashref) {
313             $$strCSV .= '"' . $hashRef->{Field} . '",';
314             push @fields, $hashRef->{Field};
315         }
316         chop $$strCSV;
317         $$strCSV .= chr(10);
318         # Populate rows with the data from mysql
319         $query = 'SELECT * FROM ' . $table . ' WHERE frameworkcode=?';
320         $sth = $dbh->prepare($query);
321         $sth->execute($frameworkcode);
322         my $data;
323         while (my $hashRef = $sth->fetchrow_hashref) {
324             for (@fields) {
325                 $hashRef->{$_} =~ s/[\r\n]//g;
326                 $$strCSV .= '"' . $hashRef->{$_} . '",';
327             }
328             chop $$strCSV;
329             $$strCSV .= chr(10);
330         }
331         $$strCSV .= chr(10);
332         for (@fields) {
333             # Separator for change of table
334             $$strCSV .= '"#-#",';
335         }
336         chop $$strCSV;
337         $$strCSV .= chr(10);
338         $$strCSV .= chr(10);
339     };
340     if ($@) {
341         $debug and warn "Error _export_table_csv $@\n";
342         return 0;
343     }
344     return 1;
345 }#_export_table_csv
346
347
348 # Export the mysql table to an ods file
349 sub _export_table_ods
350 {
351     my ($table, $dbh, $dom, $root, $frameworkcode) = @_;
352
353     eval {
354         my $elementTable = $dom->createElement('table:table');
355         $elementTable->setAttribute('table:name', $table);
356         $elementTable->setAttribute('table:print', 'false');
357         $root->appendChild($elementTable);
358         my $elementRow = $dom->createElement('table:table-row');
359         $elementTable->appendChild($elementRow);
360
361         my $elementCell;
362         my $elementData;
363         # First row with the name of the columns
364         my $query = 'SHOW COLUMNS FROM ' . $table;
365         my $sth = $dbh->prepare($query);
366         $sth->execute();
367         my @fields = ();
368         while (my $hashRef = $sth->fetchrow_hashref) {
369             $elementCell = $dom->createElement('table:table-cell');
370             $elementCell->setAttribute('office:value-type', 'string');
371             $elementCell->setAttribute('office:value', $hashRef->{Field});
372             $elementRow->appendChild($elementCell);
373             $elementData = $dom->createElement('text:p');
374             $elementCell->appendChild($elementData);
375             $elementData->appendTextNode($hashRef->{Field});
376             push @fields, {name => $hashRef->{Field}, type => ($hashRef->{Type} =~ /int/i)?'float':'string'};
377         }
378         # Populate rows with the data from mysql
379         $query = 'SELECT * FROM ' . $table . ' WHERE frameworkcode=?';
380         $sth = $dbh->prepare($query);
381         $sth->execute($frameworkcode);
382         my $data;
383         while (my $hashRef = $sth->fetchrow_hashref) {
384             $elementRow = $dom->createElement('table:table-row');
385             $elementTable->appendChild($elementRow);
386             for (@fields) {
387                 $data = $hashRef->{$_->{name}};
388                 if ($_->{type} eq 'float' && !defined($data)) {
389                     $data = '0';
390                 } elsif ($_->{type} eq 'string' && (!$data && $data ne '0')) {
391                     $data = '#';
392                 }
393                 $data = _parseContent2Xml($data) if ($_->{type} eq 'string');
394                 $elementCell = $dom->createElement('table:table-cell');
395                 $elementCell->setAttribute('office:value-type', $_->{type});
396                 $elementCell->setAttribute('office:value', $data);
397                 $elementRow->appendChild($elementCell);
398                 $elementData = $dom->createElement('text:p');
399                 $elementCell->appendChild($elementData);
400                 $elementData->appendTextNode($data);
401             }
402         }
403     };
404     if ($@) {
405         $debug and warn "Error _export_table_ods $@\n";
406         return 0;
407     }
408     return 1;
409 }#_export_table_ods
410
411
412 # Export the mysql table to an excel-xml (openoffice/libreoffice compatible) file
413 sub _export_table_excel
414 {
415     my ($table, $dbh, $dom, $root, $frameworkcode) = @_;
416
417     eval {
418         my $elementWS = $dom->createElement('Worksheet');
419         $elementWS->setAttribute('ss:Name', $table);
420         $root->appendChild($elementWS);
421         my $elementTable = $dom->createElement('ss:Table');
422         $elementWS->appendChild($elementTable);
423         my $elementRow = $dom->createElement('ss:Row');
424         $elementTable->appendChild($elementRow);
425
426         # First row with the name of the columns
427         my $elementCell;
428         my $elementData;
429         my $query = 'SHOW COLUMNS FROM ' . $table;
430         my $sth = $dbh->prepare($query);
431         $sth->execute();
432         my @fields = ();
433         while (my $hashRef = $sth->fetchrow_hashref) {
434             $elementCell = $dom->createElement('ss:Cell');
435             $elementCell->setAttribute('ss:StyleID', 's27');
436             $elementRow->appendChild($elementCell);
437             $elementData = $dom->createElement('ss:Data');
438             $elementData->setAttribute('ss:Type', 'String');
439             $elementCell->appendChild($elementData);
440             $elementData->appendTextNode($hashRef->{Field});
441             push @fields, {name => $hashRef->{Field}, type => ($hashRef->{Type} =~ /int/i)?'Number':'String'};
442         }
443         # Populate rows with the data from mysql
444         $query = 'SELECT * FROM ' . $table . ' WHERE frameworkcode=?';
445         $sth = $dbh->prepare($query);
446         $sth->execute($frameworkcode);
447         my $data;
448         while (my $hashRef = $sth->fetchrow_hashref) {
449             $elementRow = $dom->createElement('ss:Row');
450             $elementTable->appendChild($elementRow);
451             for (@fields) {
452                 $elementCell = $dom->createElement('ss:Cell');
453                 $elementRow->appendChild($elementCell);
454                 $elementData = $dom->createElement('ss:Data');
455                 $elementData->setAttribute('ss:Type', $_->{type});
456                 $elementCell->appendChild($elementData);
457                 $data = $hashRef->{$_->{name}};
458                 if ($_->{type} eq 'Number' && !defined($data)) {
459                     $data = '0';
460                 } elsif ($_->{type} eq 'String' && (!$data && $data ne '0')) {
461                     $data = '#';
462                 }
463                 $elementData->appendTextNode(($_->{type} eq 'String')?_parseContent2Xml($data):$data);
464             }
465         }
466     };
467     if ($@) {
468         $debug and warn "Error _export_table_excel $@\n";
469         return 0;
470     }
471     return 1;
472 }#_export_table_excel
473
474
475
476
477
478
479
480 # Format chars problematics to a correct format for xml.
481 sub _parseContent2Xml
482 {
483     my $content = shift;
484
485     $content =~ s/\&(?![a-zA-Z#0-9]{1,4};)/&amp;/g;
486     $content =~ s/</&lt;/g;
487     $content =~ s/>/&gt;/g;
488     return $content;
489 }#_parseContent2Xml
490
491
492 # Get the tmp directory on the system
493 sub _getTmp
494 {
495     my $tmp = '/tmp';
496     if ($ENV{'TMP'} && -d $ENV{'TMP'}) {
497         $tmp = $ENV{'TMP'};
498     } elsif ($ENV{'TMPDIR'} && -d $ENV{'TMPDIR'}) {
499         $tmp = $ENV{'TMPDIR'};
500     } elsif ($ENV{'TEMP'} && -d $ENV{'TEMP'}) {
501         $tmp = $ENV{'TEMP'};
502     }
503     return $tmp;
504 }#_getTmp
505
506
507 # Create our tempdir directory for the ods process
508 sub _createTmpDir
509 {
510     my $tmp = shift;
511
512     my $tempdir = (-d $tmp)?$tmp . '/':'./';
513     $tempdir .= 'tmp_ods_' . Digest::MD5::md5_hex(Digest::MD5::md5_hex(time().{}.rand().{}.$$));
514     eval {
515         mkdir $tempdir;
516     };
517     if ($@) {
518         return;
519     } else {
520         return $tempdir;
521     }
522 }#_createTmpDir
523
524 =head2 createODS
525
526 Creates a temporary directory to create the ods file and read it to store its content in a string.
527
528 return :
529 success
530
531 =cut
532
533 sub createODS
534 {
535     my ($strContent, $lang, $strODSRef) = @_;
536
537     my $tmp = _getTmp();
538     my $tempModule = 1;
539     my $tempdir;
540     eval {
541         require File::Temp;
542         import File::Temp qw/ tempfile tempdir /;
543         $tempdir = tempdir ( 'tmp_ods_' . $$ . '_XXXXXXXX', DIR => (-d $tmp)?$tmp:'.', CLEANUP => 1);
544     };
545     if ($@) {
546         $tempModule = 0;
547         $tempdir = _createTmpDir($tmp);
548     }
549     if ($tempdir) {
550         my $fh;
551         # populate tempdir directory with the ods elements
552         eval {
553             if (open($fh, '>',  "$tempdir/content.xml")) {
554                 print {$fh} $strContent;
555                 close($fh);
556             }
557             if (open($fh, '>', "$tempdir/mimetype")) {
558                 print {$fh} 'application/vnd.oasis.opendocument.spreadsheet';
559                 close($fh);
560             }
561             if (open($fh, '>', "$tempdir/meta.xml")) {
562                 print {$fh} _getMeta($lang);
563                 close($fh);
564             }
565             if (open($fh, '>', "$tempdir/styles.xml")) {
566                 print {$fh} ODS_STYLES_STR;
567                 close($fh);
568             }
569             if (open($fh, '>', "$tempdir/settings.xml")) {
570                 print {$fh} ODS_SETTINGS_STR;
571                 close($fh);
572             }
573             mkdir($tempdir.'/META-INF/');
574             mkdir($tempdir.'/Configurations2/');
575             mkdir($tempdir.'/Configurations2/acceleator/');
576             mkdir($tempdir.'/Configurations2/images/');
577             mkdir($tempdir.'/Configurations2/popupmenu/');
578             mkdir($tempdir.'/Configurations2/statusbar/');
579             mkdir($tempdir.'/Configurations2/floater/');
580             mkdir($tempdir.'/Configurations2/menubar/');
581             mkdir($tempdir.'/Configurations2/progressbar/');
582             mkdir($tempdir.'/Configurations2/toolbar/');
583
584             if (open($fh, '>', "$tempdir/META-INF/manifest.xml")) {
585                 print {$fh} ODS_MANIFEST_STR;
586                 close($fh);
587             }
588         };
589         if ($@) {
590             $debug and warn "Error createODS $@\n";
591         } else {
592             # create ods file from tempdir directory
593             eval {
594                 require Archive::Zip;
595                 import Archive::Zip qw( :ERROR_CODES :CONSTANTS );
596                 my $zip = Archive::Zip->new();
597                 $zip->addTree( $tempdir, '' );
598                 $zip->writeToFileNamed($tempdir . '/new.ods');
599             };
600             if ($@) {
601                 my $cmd = qx(which zip 2>/dev/null || whereis zip);
602                 chomp $cmd;
603                 $cmd = 'zip' if (!$cmd || !-x $cmd);
604                 system("cd $tempdir && $cmd -r new.ods ./");
605             }
606             my $ok = 0;
607             # read ods file and return as a string
608             if (-f "$tempdir/new.ods") {
609                 if (open ($fh, '<', "$tempdir/new.ods")) {
610                     binmode $fh;
611                     my $buffer;
612                     while (read ($fh, $buffer, 65536)) {
613                         $$strODSRef .= $buffer;
614                     }
615                     close($fh);
616                     $ok = 1;
617                 }
618             }
619             # delete tempdir directory
620             if (!$tempModule && $tempdir) {
621                 eval {
622                     require File::Path;
623                     import File::Temp qw/ rmtree /;
624                     rmtree($tempdir);
625                 };
626                 if ($@) {
627                     system("rm -rf $tempdir");
628                 }
629             }
630             return 1 if ($ok);
631         }
632     }
633     return 0;
634 }#createODS
635
636
637 # return Meta content for ods file
638 sub _getMeta
639 {
640     my $lang = shift;
641
642     my $myDate = strftime ("%Y-%m-%dT%H:%M:%S", localtime(time()));
643     my $meta = '<?xml version="1.0" encoding="UTF-8"?>
644     <office:document-meta xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:meta="urn:oasis:names:tc:opendocument:xmlns:meta:1.0" xmlns:ooo="http://openoffice.org/2004/office" office:version="1.0">
645         <office:meta>
646             <meta:generator>ods-php</meta:generator>
647             <meta:creation-date>' . $myDate . '</meta:creation-date>
648             <dc:date>' . $myDate . '</dc:date>
649             <dc:language>' . $lang . '</dc:language>
650             <meta:editing-cycles>2</meta:editing-cycles>
651             <meta:editing-duration>PT15S</meta:editing-duration>
652             <meta:user-defined meta:name="Info 1"/>
653             <meta:user-defined meta:name="Info 2"/>
654             <meta:user-defined meta:name="Info 3"/>
655             <meta:user-defined meta:name="Info 4"/>
656         </office:meta>
657     </office:document-meta>';
658     return $meta;
659 }#_getMeta
660
661
662 =head2 ImportFramework
663
664 Import all the information of a Framework from a excel-xml/ods file.
665
666 return :
667 success
668
669 =cut
670
671 sub ImportFramework
672 {
673     my ($filename, $frameworkcode, $deleteFilename) = @_;
674
675     my $tempdir;
676     my $ok = -1;
677     my $dbh = C4::Context->dbh;
678     if (-r $filename && $dbh) {
679         my $extension = '';
680         if ($filename =~ /\.(csv|ods|xml|sql)$/i) {
681             $extension = lc($1);
682         } else {
683             unlink ($filename) if ($deleteFilename); # remove temporary file
684             return -1;
685         }
686         if ($extension eq 'ods') {
687             ($tempdir, $filename) = _openODS($filename, $deleteFilename);
688         }
689         if ($filename) {
690             my $dom;
691             eval {
692                 if ($extension eq 'ods' || $extension eq 'xml') {
693                     # They have xml structure, so read it on a dom object
694                     my $parser = XML::LibXML->new();
695                     $dom = $parser->parse_file($filename);
696                     if ($dom) {
697                         my $root = $dom->documentElement();
698                     }
699                 } else {
700                     # They are text files, so open it to read
701                     open($dom, '<', $filename);
702                 }
703                 if ($dom) {
704                     # For sql we execute the line
705                     if ($extension eq 'sql') {
706                         _parseSQLLine($dbh, $dom, $frameworkcode);
707                         $ok = 0;
708                     } else {
709                         # Process both tables
710                         my $numDeleted = 0;
711                         my $numDeletedAux = 0;
712                         if (($numDeletedAux = _import_table($dbh, 'marc_tag_structure', $frameworkcode, $dom, ['frameworkcode', 'tagfield'], $extension)) >= 0) {
713                             $numDeleted += $numDeletedAux if ($numDeletedAux > 0);
714                             if (($numDeletedAux = _import_table($dbh, 'marc_subfield_structure', $frameworkcode, $dom, ['frameworkcode', 'tagfield', 'tagsubfield'], $extension)) >= 0) {
715                                 $numDeleted += $numDeletedAux if ($numDeletedAux > 0);
716                                 $ok = ($numDeleted > 0)?$numDeleted:0;
717                             }
718                         }
719                     }
720                 } else {
721                     $debug and warn "Error ImportFramework couldn't create dom\n";
722                 }
723             };
724             if ($@) {
725                 $debug and warn "Error ImportFramework $@\n";
726             } else {
727                 if ($extension eq 'sql' || $extension eq 'csv') {
728                     close($dom) if ($dom);
729                 }
730             }
731         }
732         unlink ($filename) if ($deleteFilename); # remove temporary file
733     } else {
734         $debug and warn "Error ImportFramework no conex to database or not readeable $filename\n";
735     }
736     if ($deleteFilename && $tempdir && -d $tempdir && -w $tempdir) {
737         eval {
738             require File::Path;
739             import File::Temp qw/ rmtree /;
740             rmtree($tempdir);
741         };
742         if ($@) {
743             system("rm -rf $tempdir");
744         }
745     }
746     return $ok;
747 }#ImportFramework
748
749
750 # Parse the sql statement to see if the frameworkcode is correct
751 # We're checking only the delete and insert SQL commands generated in the export process
752 sub _parseSQLLine
753 {
754     my ($dbh, $dom, $frameworkcode) = @_;
755
756     my $parser;
757     eval {
758         require SQL::Statement;
759         $parser = SQL::Parser->new('AnyData');
760         $parser->{RaiseError}=1;
761         $parser->{PrintError}=0;
762     };
763     my $literalEscape = (C4::Context->config("db_scheme") eq 'mysql')?'\\':'\'';
764     my $line;
765     my $numLines = 0;
766     while (<$dom>) {
767         s/[\r\n]+$//;
768         $line = $_;
769         # we don't want to execute any sql statement, only the ones dealing with frameworks
770         next unless ($line =~ /^\s*(?i:DELETE\s+FROM|INSERT\s+INTO)\s+(?:marc_tag_structure|marc_subfield_structure)/);
771         $numLines++;
772         # We check if the frameworkcode is the same, if not we change it
773         unless ($line =~ /'$frameworkcode'/) {
774             my $error = 0;
775             if ($parser) {
776                 eval {
777                     $line = substr($line, 0 ,-1) if ($line =~ /;$/);
778                     my $stmt = SQL::Statement->new($line, $parser);
779                     my $where = $stmt->where();
780                     if ($where && $where->op() eq '=' && $line =~ /^\s*DELETE/) {
781                         $line =~ s/frameworkcode='.*?'/frameworkcode='$frameworkcode';/ unless ($_ =~ /frameworkcode='$frameworkcode'/);
782                     } else {
783                         my @arrFields;
784                         my @arrValues;
785                         my $table;
786                         # Due to lacking of backward compatibility
787                         if ($parser->VERSION < 1.30) {
788                             $table = lc($stmt->tables(0)->name());
789                             @arrFields = map{lc($_->name)} $stmt->columns;
790                             @arrValues = $stmt->row_values();
791                         } else {
792                             $table = $stmt->tables(0)->name();
793                             @arrValues = $stmt->row_values(0);
794                             my @aux = $stmt->column_defs();
795                             for (@{$aux[0]}) {
796                                 push @arrFields, $_->{value};
797                             }
798                         }
799                         if (scalar(@arrFields) == scalar(@arrValues)) {
800                             my $j = 0;
801                             my $modified = 0;
802                             for (@arrFields) {
803                                 if ($_ eq 'frameworkcode' && $arrValues[$j] ne $frameworkcode) {
804                                     $arrValues[$j] = $dbh->quote($frameworkcode);
805                                     $modified = 1;
806                                 } else {
807                                     $arrValues[$j] = $dbh->quote($arrValues[$j]);
808                                 }
809                                 $j++;
810                             }
811                             $line = 'INSERT INTO ' . $table . ' (' . join(',', @arrFields) . ') VALUES (' . join(',', @arrValues) . ');' if ($modified);
812                         }
813                     }
814                 };
815                 $error = 1 if ($@);
816             } else {
817                 $error = 1;
818             }
819             if ($error) {
820                 $line .= ';' unless ($line =~ /;$/);
821                 if ($line =~ /^\s*DELETE/) {
822                     $line =~ s/frameworkcode='.*?'/frameworkcode='$frameworkcode'/ unless ($_ =~ /frameworkcode='$frameworkcode'/);
823                 } elsif ($line =~ /^\s*INSERT\s+INTO\s+(.*?)\s+\((.*?frameworkcode.*?)\)\s+VALUES\s+\((.+)\)\s*;\s*$/) {
824                     my $table = $1;
825                     my $fields = $2;
826                     my $values = $3;
827                     my @arrFields = split(/\s*,\s*/, $fields);
828                     my @arrValues;
829                     if ($values) {
830                         _parseSQLInsertValues($values, $literalEscape, \@arrValues);
831                     }
832                     if (scalar(@arrFields) == scalar(@arrValues)) {
833                         my $modified = 0;
834                         for (my $i=0; $i < @arrFields; $i++) {
835                             if ($arrFields[$i] eq 'frameworkcode' && $arrValues[$i]->{value} ne $frameworkcode) {
836                                 $arrValues[$i]->{value} = $dbh->quote($frameworkcode);
837                                 $modified = 1;
838                             } elsif ($arrValues[$i]->{literal}) {
839                                 $arrValues[$i]->{value} = $dbh->quote($arrValues[$i]->{value});
840                             }
841                         }
842                         if ($modified) {
843                             $line = "INSERT INTO $table ($fields) VALUES (" . join(',', map {$_->{value}} @arrValues) . ');';
844                         }
845                     }
846                 }
847             }
848         }
849         eval {
850             $dbh->do($line);
851         };
852     }
853 }#_parseSQLLine
854
855
856 # Simple sub to get the values from the insert sentence
857 sub _parseSQLInsertValues
858 {
859     my ($values, $literalEscape, $arrValues) = @_;
860
861     my ($posBegin, $posLiteral, $currentPos, $lengthValues, $currentChar);
862     $lengthValues = length($values);
863     $currentPos = 0;
864     while ($currentPos < $lengthValues) {
865         $currentChar = substr($values, $currentPos++, 1);
866         next if ($currentChar =~ /^\s$/);
867         next if ($posBegin && $currentChar !~ /^[,']$/);
868         unless ($posBegin) {
869             if ($currentChar eq '\'') {
870                 $posBegin = $currentPos;
871                 $posLiteral = $posBegin;
872             } else {
873                 $posBegin = $currentPos -1;
874             }
875         } else {
876             if ($currentChar eq ',') {
877                 unless ($posLiteral) {
878                     push @$arrValues, {literal => 0, value => substr($values, $posBegin, $currentPos -(1 + $posBegin))};
879                     $posBegin = undef;
880                 }
881             } elsif ($currentChar eq '\'' && $posLiteral) {
882                 next if ($literalEscape eq '\\' && substr($values, $currentPos -2, 1) eq $literalEscape);
883                 if ($literalEscape eq '\'' && substr($values, $currentPos, 1) eq $literalEscape) {
884                     $currentPos++;
885                     next;
886                 }
887                 push @$arrValues, {literal => 1 , value => substr($values, $posBegin, $currentPos -( 1 + $posBegin))};
888                 $currentPos++ if (substr($values, $currentPos, 1) eq ',');
889                 $posBegin = undef;
890                 $posLiteral = undef;
891             } # We shouldn't get to here if the sql sentence is correct
892         }
893    }
894    push @$arrValues, {literal => ($posLiteral)?1:0, value => substr($values, $posBegin, $currentPos - $posBegin)} if ($posBegin);
895 }#_parseSQLInsertValues
896
897
898 # Open (uncompress) ods file and return the content.xml file
899 sub _openODS
900 {
901     my ($filename, $deleteFilename) = @_;
902
903     my $tmp = _getTmp();
904     my $tempModule = 1;
905     my $tempdir;
906     eval {
907         require File::Temp;
908         import File::Temp qw/ tempfile tempdir /;
909         $tempdir = tempdir ( 'tmp_ods_' . $$ . '_XXXXXXXX', DIR => (-d $tmp)?$tmp:'.', CLEANUP => 1);
910     };
911     if ($@) {
912         $tempModule = 0;
913         $tempdir = _createTmpDir($tmp);
914     }
915     if ($tempdir) {
916         eval {
917             require Archive::Zip;
918             import Archive::Zip qw( :ERROR_CODES :CONSTANTS );
919             my $zip = Archive::Zip->new($filename);
920             foreach my $file ($zip->members) {
921                 next if ($file->isDirectory);
922                 (my $extractName = $file->fileName) =~ s{.*/}{};
923                 next unless ($extractName eq 'content.xml');
924                 $file->extractToFileNamed("$tempdir/$extractName");
925             }
926         };
927         if ($@) {
928             my $cmd = qx(which unzip 2>/dev/null || whereis unzip);
929             chomp $cmd;
930             $cmd = 'unzip' if (!$cmd || !-x $cmd);
931             system("$cmd $filename -d $tempdir");
932         }
933         if (-f "$tempdir/content.xml") {
934             unlink ($filename) if ($deleteFilename);
935             return ($tempdir, "$tempdir/content.xml");
936         }
937     }
938     unlink ($filename) if ($deleteFilename);
939     return ($tempdir, undef);
940 }#_openODS
941
942
943
944 # Check the table and columns corresponds with worksheet
945 sub _check_validity_worksheet
946 {
947     my ($dbh, $table, $nodeFields, $fieldsA, $format) = @_;
948
949     my $ret = 0;
950     eval {
951         my $query = 'DESCRIBE ' . $table;
952         my $sth = $dbh->prepare($query);
953         $sth->execute();
954         $sth->finish;
955         $query = 'SHOW COLUMNS FROM ' . $table;
956         $sth = $dbh->prepare($query);
957         $sth->execute();
958         my $fields = {};
959         while (my $hashRef = $sth->fetchrow_hashref) {
960             $fields->{$hashRef->{Field}} = $hashRef->{Field};
961         }
962         my @fields;
963         my $fieldsR;
964         if ($fieldsA) {
965             $fieldsR = $fieldsA;
966         } else {
967             $fieldsR = \@fields;
968             _getFields($nodeFields, $fieldsR, $format);
969         }
970         $ret = 1;
971         for (@$fieldsR) {
972             unless (exists($fields->{$_})) {
973                 $ret = 0;
974                 last;
975             }
976         }
977     };
978     return $ret;
979 }#_check_validity_worksheet
980
981
982 # Import the data from an excel-xml/ods to mysql tables.
983 sub _import_table
984 {
985     my ($dbh, $table, $frameworkcode, $dom, $PKArray, $format) = @_;
986     my %fields2Delete;
987     my $query;
988     my @fields;
989     # Create hash with all elements defined by primary key to know which ones to delete after parsing the spreadsheet
990     eval {
991         @fields = @$PKArray;
992         shift @fields;
993         $query = 'SELECT ' . join(',', @fields) . ' FROM ' . $table . ' WHERE frameworkcode=?';
994         my $sth = $dbh->prepare($query);
995         $sth->execute($frameworkcode);
996         my $field;
997         while (my $hashRef = $sth->fetchrow_hashref) {
998             $field = '';
999             map { $field .= $hashRef->{$_} . '_'; } @fields;
1000             chop $field;
1001             $fields2Delete{$field} = 1;
1002         }
1003         $sth->finish;
1004     };
1005     my $ok = 0;
1006     if ($format eq 'csv') {
1007         my @fieldsName = ();
1008         eval {
1009             my $query = 'SHOW COLUMNS FROM ' . $table;
1010             my $sth = $dbh->prepare($query);
1011             $sth->execute();
1012             while (my $hashRef = $sth->fetchrow_hashref) {
1013                 push @fieldsName, $hashRef->{Field};
1014             }
1015         };
1016         $ok = _import_table_csv($dbh, $table, $frameworkcode, $dom, $PKArray, \%fields2Delete, \@fieldsName);
1017     } elsif ($format eq 'ods') {
1018         $ok = _import_table_ods($dbh, $table, $frameworkcode, $dom, $PKArray, \%fields2Delete);
1019     } else {
1020         $ok = _import_table_excel($dbh, $table, $frameworkcode, $dom, $PKArray, \%fields2Delete);
1021     }
1022     if ($ok) {
1023         if (($ok = scalar(keys %fields2Delete)) > 0) {
1024             $query = 'DELETE FROM ' . $table . ' WHERE ';
1025             map {$query .= $_ . '=? AND ';} @$PKArray;
1026             $query = substr($query, 0, -4);
1027             my $sth = $dbh->prepare($query);
1028             for (keys %fields2Delete) {
1029                 eval {
1030                     $sth->execute(($frameworkcode, split('_', $_)));
1031                 };
1032             }
1033         }
1034     } else {
1035         $ok = -1;
1036     }
1037     return $ok;
1038 }#_import_table
1039
1040
1041 # Insert/Update the row from the spreadsheet in the database
1042 sub _processRow_DB
1043 {
1044     my ($dbh, $db_scheme, $table, $fields, $dataStr, $updateStr, $dataFields, $dataFieldsHash, $PKArray, $fieldsPK, $fields2Delete) = @_;
1045
1046     my $ok = 0;
1047     my $query;
1048     if ($db_scheme eq 'mysql') {
1049         $query = 'INSERT INTO ' . $table . ' (' . $fields . ') VALUES (' . $dataStr . ') ON DUPLICATE KEY UPDATE ' . $updateStr;
1050     } else {
1051         $query = 'INSERT INTO ' . $table . ' (' . $fields . ') VALUES (' . $dataStr . ')';
1052     }
1053     eval {
1054         my $sth = $dbh->prepare($query);
1055         if ($db_scheme eq 'mysql') {
1056             $sth->execute((@$dataFields, @$dataFields));
1057         } else {
1058             $sth->execute(@$dataFields);
1059         }
1060     };
1061     if ($@) {
1062         unless ($db_scheme eq 'mysql') {
1063             $query = 'UPDATE ' . $table . ' SET ' . $updateStr . ' WHERE ';
1064             map {$query .= $_ . '=? AND ';} @$PKArray;
1065             $query = substr($query, 0, -4);
1066             eval {
1067                 my $sth2 = $dbh->prepare($query);
1068                 my @dataPK = ();
1069                 map {push @dataPK, $dataFieldsHash->{$_};} @$PKArray;
1070                 $sth2->execute((@$dataFields, @dataPK));
1071             };
1072             $ok = 1 unless ($@);
1073         }
1074         $debug and warn "Error _processRows_Table $@\n";
1075     } else {
1076         $ok = 1;
1077     }
1078     if ($ok) {
1079         my $field = '';
1080         map { $field .= $dataFieldsHash->{$_} . '_'; } @$fieldsPK;
1081         chop $field;
1082         delete $fields2Delete->{$field} if (exists($fields2Delete->{$field}));
1083     }
1084     return $ok;
1085 }#_processRow_DB
1086
1087
1088 # Process the rows of a worksheet and insert/update them in a mysql table.
1089 sub _processRows_Table
1090 {
1091     my ($dbh, $frameworkcode, $nodeR, $table, $PKArray, $format, $fields2Delete) = @_;
1092
1093     my $query;
1094     my @fields = ();
1095     my $fields = '';
1096     my $dataStr = '';
1097     my $updateStr = '';
1098     my $j = 0;
1099     my $db_scheme = C4::Context->config("db_scheme");
1100     my $ok = 0;
1101     my @fieldsPK = @$PKArray;
1102     shift @fieldsPK;
1103     while ($nodeR) {
1104         if ($nodeR->nodeType == 1 && (($format && $format eq 'ods' && $nodeR->nodeName =~ /(?:table:)?table-row/) || ($nodeR->nodeName =~ /(?:ss:)?Row/)) && $nodeR->hasChildNodes()) {
1105             if ($j == 0) {
1106                 # Get name columns
1107                 _getFields($nodeR, \@fields, $format);
1108                 return 0 unless _check_validity_worksheet($dbh, $table, $nodeR, \@fields, $format);
1109                 $fields = join(',', @fields);
1110                 $dataStr = '';
1111                 map { $dataStr .= '?,';} @fields;
1112                 chop($dataStr) if ($dataStr);
1113                 $updateStr = '';
1114                 map { $updateStr .= $_ . '=?,';} @fields;
1115                 chop($updateStr) if ($updateStr);
1116             } else {
1117                 # Get data from row
1118                 my ($dataFields, $dataFieldsR) = _getDataFields($frameworkcode, $nodeR, \@fields, $format);
1119                 if (scalar(@fields) == scalar(@$dataFieldsR)) {
1120                     $ok = _processRow_DB($dbh, $db_scheme, $table, $fields, $dataStr, $updateStr, $dataFieldsR, $dataFields, $PKArray, \@fieldsPK, $fields2Delete);
1121                 }
1122             }
1123             $j++;
1124         }
1125         $nodeR = $nodeR->nextSibling;
1126     }
1127     return 1;
1128 }#_processRows_Table
1129
1130
1131
1132
1133 # Import worksheet from the csv file to the mysql table
1134 sub _import_table_csv
1135 {
1136     my ($dbh, $table, $frameworkcode, $dom, $PKArray, $fields2Delete, $fields) = @_;
1137
1138     my $row = '';
1139     my $partialRow = '';
1140     my $numFields = @$fields;
1141     my $fieldsNameRead = 0;
1142     my @arrData;
1143     my ($fieldsStr, $dataStr, $updateStr);
1144     my $db_scheme = C4::Context->config("db_scheme");
1145     my @fieldsPK = @$PKArray;
1146     shift @fieldsPK;
1147     my $ok = 0;
1148     my $numRow = 0;
1149     my $pos = 0;
1150     while (<$dom>) {
1151         $row = $_;
1152         # Check whether the line has an unfinished field, i.e., a field with CR/LF in its data
1153         if ($row =~ /,"[^"]*[\r\n]+$/ || $row =~ /^[^"]+[\r\n]+$/) {
1154             $row =~ s/[\r\n]+$//;
1155             $partialRow .= $row;
1156             next;
1157         }
1158         if ($partialRow) {
1159             $row = $partialRow . $row;
1160             $partialRow = '';
1161         }
1162         # Line OK, process it
1163         if ($row =~ /(?:".*?",?)+/) {
1164             @arrData = split('","', $row);
1165             $arrData[0] = substr($arrData[0], 1) if ($arrData[0] =~ /^"/);
1166             $arrData[$#arrData] =~ s/[\r\n]+$//;
1167             chop $arrData[$#arrData] if ($arrData[$#arrData] =~ /"$/);
1168             if (@arrData) {
1169                 if ($arrData[0] eq '#-#' && $arrData[$#arrData] eq '#-#') {
1170                     # Change of table with separators #-#
1171                     return 1;
1172                 } elsif ($fieldsNameRead && $arrData[0] eq 'tagfield') {
1173                     # Change of table because we begin with field name with former field names read
1174                     seek($dom, $pos, 0);
1175                     return 1;
1176                 }
1177                 if (scalar(@$fields) == scalar(@arrData)) {
1178                     if (!$fieldsNameRead) {
1179                         # New table, we read the field names
1180                         $fieldsNameRead = 1;
1181                         for (my $i=0; $i < @arrData; $i++) {
1182                             if ($arrData[$i] ne $fields->[$i]) {
1183                                 $fieldsNameRead = 0;
1184                                 last;
1185                             }
1186                         }
1187                         if ($fieldsNameRead) {
1188                             $fieldsStr = join(',', @$fields);
1189                             $dataStr = '';
1190                             map { $dataStr .= '?,';} @$fields;
1191                             chop($dataStr) if ($dataStr);
1192                             $updateStr = '';
1193                             map { $updateStr .= $_ . '=?,';} @$fields;
1194                             chop($updateStr) if ($updateStr);
1195                         }
1196                     } else {
1197                         # Read data
1198                         my $j = 0;
1199                         my %dataFields = ();
1200                         for (@arrData) {
1201                             if ($fields->[$j] eq 'frameworkcode' && $_ ne $frameworkcode) {
1202                                 $dataFields{$fields->[$j]} = $frameworkcode;
1203                                 $arrData[$j] = $frameworkcode;
1204                             } else {
1205                                 $dataFields{$fields->[$j]} = $_;
1206                             }
1207                             $j++
1208                         }
1209                         $ok = _processRow_DB($dbh, $db_scheme, $table, $fieldsStr, $dataStr, $updateStr, \@arrData, \%dataFields, $PKArray, \@fieldsPK, $fields2Delete);
1210                     }
1211                 }
1212                 $pos = tell($dom);
1213             }
1214             @arrData = ();
1215         }
1216         $numRow++;
1217     }
1218     return $ok;
1219 }#_import_table_csv
1220
1221
1222 # Import worksheet from the ods content.xml file to the mysql table
1223 sub _import_table_ods
1224 {
1225     my ($dbh, $table, $frameworkcode, $dom, $PKArray, $fields2Delete) = @_;
1226
1227     my $xc = XML::LibXML::XPathContext->new($dom);
1228     $xc->registerNs('xmlns:office','urn:oasis:names:tc:opendocument:xmlns:office:1.0');
1229     $xc->registerNs('xmlns:table','urn:oasis:names:tc:opendocument:xmlns:table:1.0');
1230     $xc->registerNs('xmlns:text','urn:oasis:names:tc:opendocument:xmlns:text:1.0');
1231     my @nodes;
1232     @nodes = $xc->findnodes('//table:table[@table:name="' . $table . '"]');
1233     if (@nodes == 1 && $nodes[0]->hasChildNodes()) {
1234         my $nodeR = $nodes[0]->firstChild;
1235         return _processRows_Table($dbh, $frameworkcode, $nodeR, $table, $PKArray, 'ods', $fields2Delete);
1236     } else {
1237         $debug and warn "Error _import_table_ods there's not worksheet for $table\n";
1238     }
1239     return 0;
1240 }#_import_table_ods
1241
1242
1243 # Import worksheet from the excel-xml file to the mysql table
1244 sub _import_table_excel
1245 {
1246     my ($dbh, $table, $frameworkcode, $dom, $PKArray, $fields2Delete) = @_;
1247
1248     my $xc = XML::LibXML::XPathContext->new($dom);
1249     $xc->registerNs('xmlns','urn:schemas-microsoft-com:office:spreadsheet');
1250     $xc->registerNs('xmlns:ss','urn:schemas-microsoft-com:office:spreadsheet');
1251     $xc->registerNs('xmlns:x','urn:schemas-microsoft-com:office:excel');
1252     my @nodes;
1253     @nodes = $xc->findnodes('//ss:Worksheet[@ss:Name="' . $table . '"]');
1254     if (@nodes > 0) {
1255         for (my $i=0; $i < @nodes; $i++) {
1256             my @nodesT = $nodes[$i]->getElementsByTagNameNS('urn:schemas-microsoft-com:office:spreadsheet', 'Table');
1257             if (@nodesT == 1 && $nodesT[0]->hasChildNodes()) {
1258                 my $nodeR = $nodesT[0]->firstChild;
1259                 return _processRows_Table($dbh, $frameworkcode, $nodeR, $table, $PKArray, undef, $fields2Delete);
1260             }
1261         }
1262     } else {
1263         $debug and warn "Error _import_table_excel there's not worksheet for $table\n";
1264     }
1265     return 0;
1266 }#_import_table_excel
1267
1268
1269 # Get the data from a cell on a ods file through the value attribute or the text node
1270 sub _getDataNodeODS
1271 {
1272     my $node = shift;
1273
1274     my $data;
1275     my $repeated = 0;
1276     if ($node->nodeType == 1 && $node->nodeName =~ /(?:table:)?table-cell/) {
1277         if ($node->hasAttributeNS('urn:oasis:names:tc:opendocument:xmlns:office:1.0', 'value')) {
1278             $data = $node->getAttributeNS('urn:oasis:names:tc:opendocument:xmlns:office:1.0', 'value');
1279         } elsif ($node->hasChildNodes()) {
1280             my @nodes2 = $node->getElementsByTagNameNS('urn:oasis:names:tc:opendocument:xmlns:text:1.0', 'p');
1281             if (@nodes2 == 1 && $nodes2[0]->hasChildNodes()) {
1282                 $data = $nodes2[0]->firstChild->nodeValue;
1283             }
1284         }
1285         if ($node->hasAttributeNS('urn:oasis:names:tc:opendocument:xmlns:table:1.0', 'number-columns-repeated')) {
1286             $repeated = $node->getAttributeNS('urn:oasis:names:tc:opendocument:xmlns:table:1.0', 'number-columns-repeated');
1287         }
1288     }
1289     return ($data, $repeated);
1290 }#_getDataNodeODS
1291
1292
1293 # Get the data from a row of a spreadsheet
1294 sub _getDataFields
1295 {
1296     my ($frameworkcode, $node, $fields, $format) = @_;
1297
1298     my $dataFields = {};
1299     my @dataFieldsA = ();
1300     if ($node && $node->hasChildNodes()) {
1301         my $node2 = $node->firstChild;
1302         my ($data, $repeated);
1303         my $i = 0;
1304         my $ok = 0;
1305         $repeated = 0;
1306         while ($node2) {
1307             if ($format && $format eq 'ods') {
1308                 ($data, $repeated) = _getDataNodeODS($node2) if ($repeated <= 0);
1309                 $repeated--;
1310                 $ok = 1 if (defined($data));
1311             } else {
1312                 if ($node2->nodeType == 1 && $node2->nodeName  =~ /(?:ss:)?Cell/) {
1313                     my @nodes3 = $node2->getElementsByTagNameNS('urn:schemas-microsoft-com:office:spreadsheet', 'Data');
1314                     if (@nodes3 == 1 && $nodes3[0]->hasChildNodes()) {
1315                         $data = $nodes3[0]->firstChild->nodeValue;
1316                         $ok = 1;
1317                     }
1318                 }
1319             }
1320             if ($ok) {
1321                 $data = '' if ($data eq '#');
1322                 $data = $frameworkcode if ($fields->[$i] eq 'frameworkcode');
1323                 $dataFields->{$fields->[$i]} = $data;
1324                 push @dataFieldsA, $data;
1325                 $i++;
1326             }
1327             $ok = 0;
1328             $node2 = $node2->nextSibling if ($repeated <= 0);
1329         }
1330     }
1331     return ($dataFields, \@dataFieldsA);
1332 }#_getDataFields
1333
1334
1335 # Get the data from the first row to know the column names
1336 sub _getFields
1337 {
1338     my ($node, $fields, $format) = @_;
1339
1340     if ($node && $node->hasChildNodes()) {
1341         my $node2 = $node->firstChild;
1342         my ($data, $repeated);
1343         while ($node2) {
1344             if ($format && $format eq 'ods') {
1345                 ($data, $repeated) = _getDataNodeODS($node2);
1346                 push @$fields, $data if (defined($data));
1347             } else {
1348                 if ($node2->nodeType == 1 && $node2->nodeName =~ /(?:ss:)?Cell/) {
1349                     my @nodes3 = $node2->getElementsByTagNameNS('urn:schemas-microsoft-com:office:spreadsheet', 'Data');
1350                     if (@nodes3 == 1 && $nodes3[0]->hasChildNodes()) {
1351                         $data = $nodes3[0]->firstChild->nodeValue;
1352                         push @$fields, $data;
1353                     }
1354                 }
1355             }
1356             $node2 = $node2->nextSibling;
1357         }
1358     }
1359 }#_getFields
1360
1361
1362
1363
1364 1;
1365 __END__
1366
1367 =head1 AUTHOR
1368
1369 Koha Development Team <http://koha-community.org/>
1370
1371 =cut
1372
1373