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> </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