1<?php
2/**
3 * address_book_import.php
4 *
5 * Copyright (c) 1999-2006 The SquirrelMail Project Team
6 * Copyright (c) 2007 Tomas Kuliavas <tokul@users.sourceforge.net>
7 * Licensed under the GNU GPL. For full terms see the file COPYING.
8 *
9 * Import csv files for address book
10 * This takes a comma delimited file uploaded from addressbook.php
11 * and allows the user to rearrange the field order to better
12 * fit the address book. A subset of data is manipulated to save time.
13 * @version $Id: address_book_import.php,v 1.26 2007/06/09 08:38:42 tokul Exp $
14 * @package sm-plugins
15 * @subpackage abook_import_export
16 */
17
18/** SquirrelMail init */
19if (file_exists('../../include/init.php')) {
20    /* sm 1.5.2+*/
21
22    /* main init script */
23    include_once('../../include/init.php');
24} else {
25    /* sm 1.4.0+ */
26
27    /** @ignore */
28    define('SM_PATH', '../../');
29    /* main init script */
30    include_once(SM_PATH . 'include/validate.php');
31}
32
33/* load address book functions */
34include_once(SM_PATH . 'functions/addressbook.php');
35/* load sqm_baseuri() (sm 1.4.0-1.4.5,1.5.0) function */
36include_once(SM_PATH . 'functions/display_messages.php');
37/* load own functions */
38include_once(SM_PATH . 'plugins/abook_import_export/functions.php');
39
40// Local Variables
41$errorstring = '';
42$finish = '';
43$csvmax = 0;
44$key = 0;
45$x = 0;
46$row = 0;
47$cols = 0;
48$colspan = 0;
49$c = 0;
50$error = 0;
51$reorg = array();
52$selrow = '';
53
54// FIXME: not sure if global declarations are needed
55global $color, $squirrelmail_language, $default_charset;
56
57// Make sure that $default_charset is set to correct value
58set_my_charset();
59
60if (! sqGetGlobalVar('finish',$finish,SQ_POST)) {
61    // Stage 1. Process uploaded file
62
63    displayPageHeader($color, "None");
64
65    // initialize address book. don't display errors. don't init remote backends
66    // object is used by form. do it now in order to avoid domain switching.
67    $abook = addressbook_init(false, true);
68
69    // switch domain
70    bindtextdomain('abook_import_export',SM_PATH . 'locale');
71    textdomain('abook_import_export');
72
73    if (function_exists('bind_textdomain_codeset')) {
74        if ($squirrelmail_language == 'ja_JP') {
75            bind_textdomain_codeset ('abook_import_export', 'EUC-JP');
76        } else {
77            bind_textdomain_codeset ('abook_import_export', $default_charset );
78        }
79    }
80
81    // Check to make sure the user actually put a file in the upload file box.
82    $smusercsv = $_FILES['smusercsv'];
83
84    if ($smusercsv['tmp_name'] == '' || $smusercsv['size'] == 0) {
85        // Detect PHP 4.2.0+ upload error codes (http://www.php.net/features.file-upload.errors)
86        $upload_error = _("Please select a file for uploading.");
87        if (isset($smusercsv['error']) && $smusercsv['error']!=0 ) {
88            switch($smusercsv['error']) {
89            case 1:
90                $upload_error = _("The uploaded file exceeds PHP upload_max_filesize limits.");
91                break;
92            case 2:
93                $upload_error = _("The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML.");
94                break;
95            case 3:
96                $upload_error = _("The uploaded file was only partially uploaded.");
97                break;
98            case 4:
99                $upload_error = _("No file was uploaded.");
100                break;
101            case 6:
102                $upload_error = _("Missing a temporary directory.");
103                break;
104            case 7:
105                $upload_error = _("Failed to write file to disk.");
106                break;
107            case 8:
108                // File upload stopped by extension. 'security library' is more user friendly.
109                $upload_error = _("File upload stopped by security library.");
110                break;
111            default:
112                $upload_error = _("Unknown upload error.");
113                break;
114            }
115        }
116
117        $error_msg = html_tag('p',$upload_error)
118            .html_tag('p',sprintf(_("Return to main %sAddress Book%s page."),
119                                  '<a href="' . sqm_baseuri() .  'src/addressbook.php">',
120                                  '</a>'), 'center');
121        aie_error_box($error_msg,_("Upload error"),true);
122    } elseif ( $smusercsv['size'] > $aie_csv_maxsize ) {
123        // i18n: %s displays 'somenumber B', 'somenumber KB' or 'somenumber MB'.
124        $error_msg = sprintf(_("Imported CSV file is too big. Contact your system administrator, if you want to import files, that are bigger than %s."),aie_display_size($aie_csv_maxsize));
125        aie_error_box($error_msg,'',true);
126    }
127
128    /**
129     * Remove old csvdata from session and set initial $csvdata and $csvorder values.
130     * $csvdata stores imported data. $csvorder is used by aie_CSVProcess() to detect
131     * order of imported fields. variable is accessed through references in order
132     * to avoid globalization of the variable. $error can store fatal processing errors.
133     */
134    sqsession_unregister('csvdata');
135    $csvdata = array();
136    $csvorder = array();
137    $error = '';
138
139    // find unused file name in attachment directory.
140    $temp_file = md5($smusercsv['tmp_name']);
141
142    // handle misconfigured $attachment_dir
143    if (preg_match('/^[a-z]\:\\\\/i',$attachment_dir)) {
144        // windows full path x:\something. relative path issues are ignored, because they will
145        // require mods in all SquirrelMail scripts.
146        if (substr($attachment_dir,-1,1)!='\\') {
147            $attachment_dir.= '\\';
148        }
149    } elseif (substr($attachment_dir,-1,1)!='/') {
150        $attachment_dir.= '/';
151    }
152
153    while (file_exists($attachment_dir . $temp_file)) {
154        // calculate new md5sum until we find place to store data
155        $temp_file = md5($temp_file);
156    }
157
158    // don't open uploaded file directly. Move it to SM temp directory before using it.
159    if (@move_uploaded_file($smusercsv['tmp_name'],$attachment_dir . $temp_file)) {
160
161        $csvfile = fopen($attachment_dir . $temp_file,"r");
162
163        if (!$csvfile) {
164            echo '<br><br>'
165                .'<table align="center">'
166                .'<tr><td>'
167                ._("Error, could not open address file.")
168                .'</td></tr>'
169                .'</table>';
170            exit;
171        }
172
173        if (! sqgetGlobalVar('field_delimiter',$field_delimiter,SQ_POST) ||
174            ! in_array($field_delimiter,array("'",'"',',',';','custom'))) {
175            $field_delimiter = ',';
176        } elseif ($field_delimiter=='custom') {
177            if (! sqgetGlobalVar('custom_field_delimiter',$field_delimiter,SQ_POST)) {
178                $field_delimiter = ',';
179            }
180        }
181
182        if (check_php_version(4,3,0) &&
183            sqgetGlobalVar('text_delimiter',$text_delimiter,SQ_POST) &&
184            in_array($text_delimiter,array("'",'"',',',';','custom'))) {
185
186            if ($text_delimiter=='custom') {
187                if (! sqgetGlobalVar('custom_text_delimiter',$text_delimiter,SQ_POST)) {
188                    $text_delimiter = '"';
189                }
190            }
191
192            // compare text and field delimiters
193            if ($text_delimiter == $field_delimiter) {
194                $error_msg = _("You must use different symbols for text and field delimiters.");
195                aie_error_box($error_msg,'',true);
196            }
197        } else {
198            if ($field_delimiter=='"') {
199                $text_delimiter = "'";
200            } else {
201                $text_delimiter = '"';
202            }
203        }
204
205        // use own wrapper to solve differences between 4.3.0+ and older
206        while ($csvarray = aie_fgetcsv($csvfile,$smusercsv['size'],$field_delimiter,$text_delimiter)) {
207            // Let fgetcsv deal with splitting the line into it's parts. (I.E. it deals with quoted commas right.
208            $temp = aie_CSVProcess($csvarray,$text_delimiter,$csvorder);
209
210            if (is_string($temp)) {
211                $error = $temp;
212                // remove all processed data
213                $csvdata = array();
214                // remove all line processing errors
215                $errorstring = '';
216                // stop csv processing
217                break;
218            } elseif (count($temp) >1) {
219                $csvdata[$key] = $temp;
220                $key++;
221            } elseif (isset($temp[0]) && !empty($temp[0])) {
222                // row returned only one element or delimiter was not correct
223                $errorstring .= '<li>' . htmlentities($temp[0]) . '</li>';
224            }
225
226            // After this, the function was just doing some calculations, and returned without a problem.
227            if(count($csvarray) > $csvmax) {
228                $csvmax = count($csvarray);
229            }
230        }
231
232        // close file handle
233        fclose($csvfile);
234        // remove uploaded file
235        unlink($attachment_dir . $temp_file);
236
237        /* Compact imported data (csv can store empty fields) */
238
239        // array_fill() is available only in php 4.2+
240        // $clean_array = array_fill(0,$csvmax,true);
241        $clean_array = array();
242        foreach ($csvdata as $idx => $entry) {
243            // detect empty columns
244            for($i = 0; $i < $csvmax; $i++) {
245                if (!empty($entry[$i])) {
246                    $clean_array[$i] = false;
247                } elseif (!isset($clean_array[$i])) {
248                    // see array_fill() comments
249                    $clean_array[$i] = true;
250                }
251            }
252        }
253        // Unset empty columns
254        foreach ($csvdata as $idx => $entry) {
255            for($i = 0; $i < $csvmax; $i++) {
256                // don't touch first four columns in order to preserve
257                // firstname (1), lastname (2), email (3) order
258                if ($i > 3 && $clean_array[$i]) unset($csvdata[$idx][$i]);
259            }
260        }
261        // Rebuild array index
262        $new_csvdata = array();
263        foreach ($csvdata as $entry) {
264            array_push($new_csvdata,array_values($entry));
265        }
266        $csvdata = $new_csvdata;
267        // Get new column counter
268        $csvmax = 0;
269        foreach ($csvdata as $entry) {
270            $count = count($entry);
271            if ($csvmax < $count) $csvmax = $count;
272        }
273        /* End of compacting code */
274
275        // create final import form only when some data is available
276        if (count($csvdata) > 0) {
277            echo '<form method="post" action="' . $PHP_SELF . '">';
278
279            echo '<center><table width="95%" frame="void" cellspacing="1">';    // user's data table
280
281            // Here I will create the headers that I want.
282            echo '<tr bgcolor="' . $color[9] . '" align="center">';
283            // Title of column with row numbers
284            echo '<td width="1">' .  _("No#") . '</td>';
285            // Title of column with omit checkbox
286            echo '<td width="1">' .  _("Omit") . '</td>';
287
288            for($x = 0; $x < $csvmax; $x++) { // The Drop down boxes to select what each column is
289                echo '<td>';
290                aie_create_Select($csvmax,$x);
291                echo '</td>';
292            }
293
294            echo '</tr>';
295
296            while ($row < count($csvdata)) {
297                if (count($csvdata[$row]) >= 5) {    // This if ensures the minimum number of columns
298                    $cols = count($csvdata[$row]);    // so importing can function for all 5 fields
299                } else {
300                    $cols = 5;
301                }
302
303                // unused
304                // $colspan = $cols + 1;
305
306                if ($row % 2) {                   // Set up the alternating colored rows
307                    echo '<tr bgcolor="' . $color[0] . '">';
308                } else {
309                    echo '<tr>';
310                }
311
312                // print row number (start counter from 1 and not from 0)
313                echo '<td width="1" align="center">' . ($row+1) . '</td>';
314                // Print the omit checkbox, to be checked before write
315                echo '<td width="1" align="center"><input type="checkbox" name="sel' . $row . '"></td>';
316
317                for($c = 0; $c < $cols; $c++) { // For each column in the current row
318                    if (isset($csvdata[$row][$c])
319                        && $csvdata[$row][$c] != '') {
320                        // if not empty, put data in cell.
321                        echo '<td NOWRAP>' . $csvdata[$row][$c] . '</td>';
322                    } else {
323                        // if empty, put space in cell keeping colors correct.
324                        echo '<td>&nbsp;</td>';
325                    }
326                }
327                echo '</tr>';
328                $row++;
329            }
330
331            echo '</table></center><br />';
332
333            // save uploaded and processed csv data in session
334            sqsession_register($csvdata,'csvdata');
335
336            $form=aie_select_backend('write',$bcount);
337            if ($bcount>1) {
338                echo _("Add to address book: ");
339                echo aie_select_backend('write',$bcount);
340                echo "<br />\n";
341            } else {
342                echo $form;
343            }
344            // display import button only after table is loaded
345            echo '<input type="submit" name="finish" value="' . _("Finish") . '" tabindex="4">';
346            echo '</form>';
347        } else {
348            /**
349             * $csvdata is empty. User tried to import empty file or $error contains fatal
350             * processing error message.
351             */
352            if (empty($error)) $error = _("Nothing to import");
353            $error .= '<br /><p align="center"><a href="' . sqm_baseuri() . 'src/addressbook.php">' . _("Return to Address Book") . '</a></p>';
354            aie_error_box($error);
355        }
356
357        if(strlen($errorstring)) {
358            echo _("The following rows have errors")
359                . ': <br /><ul>' . $errorstring . '</ul>';
360        }
361    } else {
362        // unable to move file to temp directory
363        aie_error_box(_("Can't move uploaded file to attachment directory."));
364    }
365} else {
366    // Stage 2. save addresses
367
368    // Since we will print something to the page at this point
369    displayPageHeader($color, 'None');
370
371    /** create address book object without remote backends */
372    $abook = addressbook_init(false, true);
373
374    if (!empty($abook->error)) {
375        aie_error_box(nl2br(htmlspecialchars($abook->error)),'',true);
376    }
377
378    /* set domain, but don't switch it. we need domain for dgettext calls and
379     * main code is still running in squirrelmail domain.
380     */
381    bindtextdomain('abook_import_export',SM_PATH . 'locale');
382    if (function_exists('bind_textdomain_codeset')) {
383        if ($squirrelmail_language == 'ja_JP') {
384            bind_textdomain_codeset ('abook_import_export', 'EUC-JP');
385        } else {
386            bind_textdomain_codeset ('abook_import_export', $default_charset );
387        }
388    }
389
390    /* get csvdata from session */
391    if (! sqGetGlobalVar('csvdata',$csvdata,SQ_SESSION) || ! is_array($csvdata)) {
392        // $csvdata is not available or is not array.
393        $error_msg = html_tag('p',_("Unable to access uploaded data. Contact your system administrator."))
394            .html_tag('p',sprintf(_("Return to main %sAddress Book%s page."),
395                                  '<a href="' . sqm_baseuri() .  'src/addressbook.php">',
396                                  '</a>'), 'center');
397        aie_error_box($error_msg,'',true);
398    }
399
400    while($row < count($csvdata)) {
401        if (count($csvdata[$row]) >= 5) {    // This if ensures the minimum number of columns
402            $cols = count($csvdata[$row]);    // so importing can function for all 5 fields
403        } else {
404            $cols = 5;
405        }
406
407        $reorg = array('', '', '', '', '');
408
409        for ($c=0; $c < $cols; $c++) {
410            // Reorganize the data to fit the header cells that the user chose
411            // concatenate fields based on user input into text boxes.
412            $column = "COL$c";
413
414            // check if form posts call needed columns
415            if(sqGetGlobalVar($column,$colno,SQ_POST)) {
416                if($colno != 5)  {
417                    if ($colno == 4) {
418                        // label field is optional. It might be missing in some wierd csv imports:
419                        $reorg[4] .= (isset($csvdata[$row][$c]) ? $csvdata[$row][$c] : '') . ";";
420                    } else {
421                        $reorg[$colno] = $csvdata[$row][$c];
422                        $reorg[$c] = trim($reorg[$c],"\r\n \"");
423                    }
424                }
425            }
426        }
427
428        if (isset($reorg[4])) {
429            $reorg[4] = trim($reorg[4],";");
430        }
431
432        $csvdata[$row] = $reorg;
433        unset($reorg); // So that we don't get any weird information from a previous rows
434
435        // If finished, do the import. This uses Pallo's excellent class and object stuff
436        $selrow = 'sel' . $row;
437
438        // import row only when Omit option is not set.
439        if (! sqGetGlobalVar($selrow,$testvar,SQ_POST)) {
440            if (eregi('[ \\:\\|\\#\\"\\!]', $csvdata[$row][0])) {
441                $csvdata[$row][0] = '';
442            }
443
444            // Here we should create the right data to input
445            if (count($csvdata[$row]) < 5) {
446                array_pad($csvdata[$row],5,'');
447            }
448
449            $addaddr['nickname']  = $csvdata[$row][0];
450            $addaddr['firstname'] = $csvdata[$row][1];
451            $addaddr['lastname']  = $csvdata[$row][2];
452            $addaddr['email']     = $csvdata[$row][3];
453            $addaddr['label']     = $csvdata[$row][4];
454
455            if (! sqGetGlobalVar('backend',$backend,SQ_POST)) {
456                $backend=$abook->localbackend;
457            } else {
458                // make sure that it is integer
459                $backend=(int) $backend;
460            }
461
462            if ( ! $abook->add($addaddr,$backend)) {
463                // displays row number that can't be imported. SquirrelMail
464                // address book backend error message is displayed after it.
465                $errorstring .= sprintf(dgettext('abook_import_export',"Row %d:"),($row+1)) . ' ' . $abook->error . "<br />\n";
466                $error++;
467            }
468
469            unset($addaddr); // Also so we don't get any weird information from previous rows
470        }
471
472        $row++;
473    }
474
475    // Now that we've uploaded this information, we dont' need this variable anymore, aka cleanup
476    session_unregister('csvdata');
477
478    textdomain('abook_import_export');
479
480    // Print out that we've completed this operation
481    if ($error) {
482        echo '<br />'
483            . _("There were errors uploading the data, as listed below. Entries not listed here were uploaded.")
484            . '<br /> ' . $errorstring . '<br /> ';
485    } else {
486        echo '<br /><br /><center><h1><strong>'
487            ._("Upload Completed!")
488            .'</strong></h1>'
489            .'<p>' . _("Click on the link below to verify your work.") . '</p>'
490            .'</center>';
491    }
492
493    echo '<br /><br /><p align="center"><a href="' . sqm_baseuri() . 'src/addressbook.php">' . _("Addresses") . '</a></p>';
494
495}
496?>
497</body>
498</html>
499