1<?php
2/**
3 * functions.php - Addressbook Import-Export functions
4 *
5 * Copyright (c) 1999-2006 The SquirrelMail Project Team
6 * Licensed under the GNU GPL. For full terms see the file COPYING.
7 *
8 * Uses standard plugin format to create a couple of forms to
9 * enable import/export of CSV files to/from the address book.
10 * @version $Id: functions.php,v 1.24 2006/07/22 16:45:08 tokul Exp $
11 * @package sm-plugins
12 * @subpackage abook_import_export
13 */
14
15/** @ignore */
16if (!defined('SM_PATH')) define('SM_PATH','../../');
17
18/** load sqm_baseuri() function */
19include_once(SM_PATH . 'functions/display_messages.php');
20/** load form functions */
21include_once(SM_PATH . 'functions/forms.php');
22
23/** set configuration globals */
24global $aie_csv_maxsize, $aie_input_charsets, $aie_hide_upload_error;
25
26/** load default configuration*/
27if (file_exists(SM_PATH . 'plugins/abook_import_export/config_default.php')) {
28    include_once(SM_PATH . 'plugins/abook_import_export/config_default.php');
29} else {
30    // set default config values inside script, if file is removed.
31    $aie_csv_maxsize=5120;
32    // input character sets
33    $aie_input_charsets = array(
34         'windows-1250',
35         'windows-1251',
36         'windows-1252',
37         'windows-1253',
38         'windows-1254',
39         'windows-1255',
40         'windows-1256',
41         'windows-1257',
42         'windows-1258',
43         'cp855',
44         'cp866',
45         'iso-8859-10',
46         'iso-8859-11',
47         'iso-8859-13',
48         'iso-8859-14',
49         'iso-8859-15',
50         'iso-8859-16',
51         'iso-8859-1',
52         'iso-8859-2',
53         'iso-8859-3',
54         'iso-8859-4',
55         'iso-8859-5',
56         'iso-8859-6',
57         'iso-8859-7',
58         'iso-8859-8',
59         'iso-8859-9',
60         'iso-ir-111',
61         'koi8-r',
62         'koi8-u',
63         'ns_4551_1',
64         'tis-620',
65         'us_ascii',
66         'utf-8');
67    $aie_hide_upload_error=false;
68}
69
70/** site configuration */
71if (file_exists(SM_PATH . 'config/abook_import_export_config.php')) {
72    include_once(SM_PATH . 'config/abook_import_export_config.php');
73} elseif (file_exists(SM_PATH . 'plugins/abook_import_export/config.php')) {
74    include_once(SM_PATH . 'plugins/abook_import_export/config.php');
75}
76
77/* Sort input character sets */
78natsort($aie_input_charsets);
79
80
81/**
82 * Add import/export form
83 * (internal function)
84 */
85function aie_create_form() {
86    global $color,$aie_csv_maxsize, $aie_input_charsets, $default_charset, $aie_hide_upload_error,
87        $squirrelmail_language;
88
89    // switch domain
90    bindtextdomain('abook_import_export',SM_PATH . 'locale');
91    textdomain('abook_import_export');
92
93    if (function_exists('bind_textdomain_codeset')) {
94        if ($squirrelmail_language == 'ja_JP') {
95            bind_textdomain_codeset ('abook_import_export', 'EUC-JP');
96        } else {
97            bind_textdomain_codeset ('abook_import_export', $default_charset );
98        }
99    }
100
101    $aie_delimiter = array("'"=>_("Single quotes (')"),
102                           '"'=>_("Double quotes (\")"),
103                           ','=>_("Comma (,)"),
104                           ';'=>_("Semicolon (;)"),
105                           'custom'=>_("Custom delimiter"));
106
107    // using php for html generation, because formating of mixed php/html code is not good.
108
109    if ((bool)ini_get('file_uploads')) {
110        echo html_tag( 'table',
111            html_tag( 'tr',
112                html_tag( 'td', '<strong>' . _("Address book import") . '</strong>' . "\n", 'center', $color[0] )
113             ),
114            'center', '', 'width="95%"' );
115
116        echo "<!-- begin csv import form -->\n";
117        // don't use MAX_FILE_SIZE input field or don't rely on it.
118        // Size can't be controlled in place that can be modified by end user.
119        echo '<form enctype="multipart/form-data" action="'
120            .sqm_baseuri() . 'plugins/abook_import_export/address_book_import.php'
121            .'" method="post">' . "\n";
122
123        echo '<table width="90%" border="0" cellpadding="1" cellspacing="0" align="center">' . "\n";
124        echo html_tag('tr',
125             html_tag('td',_("Select file:"),'right')."\n".
126             html_tag('td',addHidden('MAX_FILE_SIZE',$aie_csv_maxsize).
127                           '<input name="smusercsv" type="file" />','left'));
128
129        echo html_tag('tr',
130             html_tag('td',_("Max:"),'right') ."\n".
131             html_tag('td',aie_display_size($aie_csv_maxsize),'left'));
132
133        echo html_tag('tr',
134             html_tag('td',_("Input character set:"),'right')."\n".
135             html_tag('td',addSelect('input_charset',$aie_input_charsets,$default_charset),'left'));
136
137        echo html_tag('tr',
138             html_tag('td',_("Field delimiter:"),'right')."\n".
139             html_tag('td',addSelect('field_delimiter',$aie_delimiter,',',true),'left'));
140
141        echo html_tag('tr',
142             html_tag('td',_("Custom field delimiter:"),'right')."\n".
143             html_tag('td',addInput('custom_field_delimiter',',',1,1),'left'));
144
145        /* fgetcsv enclosure option is available since 4.3.0 */
146        if (check_php_version(4,3,0)) {
147            echo html_tag('tr',
148                 html_tag('td',_("Text delimiter:"),'right')."\n".
149                 html_tag('td',addSelect('text_delimiter',$aie_delimiter,'"',true),'left'));
150
151            echo html_tag('tr',
152                 html_tag('td',_("Custom text delimiter:"),'right')."\n".
153                 html_tag('td',addInput('custom_text_delimiter','"',1,1),'left'));
154        }
155
156        echo html_tag('tr',
157             html_tag('td',addSubmit(_("Import CSV File")),'center','','colspan="2"'));
158
159        echo "</table>\n";
160        echo "</form>\n";
161        echo "<!-- end csv import form -->\n";
162    } elseif (!$aie_hide_upload_error) {
163        echo html_tag('table',
164             html_tag('tr',
165             html_tag('td', '<font color="'.$color[2].'"><strong>' . _("ERROR") . '</strong></font>', 'center', $color[0] ) ).
166             html_tag('tr',
167             html_tag('td', _("Address book uploads are disabled."), 'center' ) ),
168             'center', '', 'width="95%"' );
169    }
170
171    echo "<br />\n";
172
173    echo html_tag( 'table',
174         html_tag( 'tr',
175             html_tag( 'td', '<strong>' . _("Address book export") . '</strong>' . "\n", 'center', $color[0] )
176             ),
177         'center', '', 'width="95%"' );
178
179    echo "<!-- begin csv export form -->\n";
180    echo '<form ENCTYPE="multipart/form-data" action="'
181        .sqm_baseuri() . 'plugins/abook_import_export/address_book_export.php'
182        .'" method="post">';
183
184    echo '<table width="90%" border="0" cellpadding="1" cellspacing="0" align="center">' . "\n";
185
186    echo html_tag('tr',
187                  html_tag('td',_("Field delimiter:"),'right')."\n".
188                  html_tag('td',addSelect('field_delimiter',$aie_delimiter,',',true),'left'));
189
190    echo html_tag('tr',
191                  html_tag('td',_("Custom field delimiter:"),'right')."\n".
192                  html_tag('td',addInput('custom_field_delimiter',',',1,1),'left'));
193
194    /**
195     * fgetcsv enclosure option is available since 4.3.0.
196     * plugin uses code that doesn't depend on 4.3+ functions.
197     * we leave same options in order to create backwards compatible exports.
198     */
199    if (check_php_version(4,3,0)) {
200        echo html_tag('tr',
201             html_tag('td',_("Text delimiter:"),'right')."\n".
202             html_tag('td',addSelect('text_delimiter',$aie_delimiter,'"',true),'left'));
203
204        echo html_tag('tr',
205             html_tag('td',_("Custom text delimiter:"),'right')."\n".
206             html_tag('td',addInput('custom_text_delimiter','"',1,1),'left'));
207    }
208
209    $form=aie_select_backend('list',$bcount);
210    if ($bcount>1) {
211        echo html_tag('tr',
212             html_tag('td',_("Use address book:"),'right')."\n".
213             html_tag('td',$form,'left'));
214    } else {
215        echo $form;
216    }
217
218    echo html_tag('tr',
219                  html_tag('td',addSubmit(_("Export to CSV File")),'center','','colspan="2"'));
220
221    echo "</table>";
222    echo "</form>\n";
223    echo "<!-- end csv export form -->\n";
224
225    // revert domain
226    textdomain('squirrelmail');
227}
228
229/**
230 * returns size integer formated in bytes, Kbytes or Mbytes
231 * @param integer $size size in bytes
232 * @return string formated size string
233 */
234function aie_display_size($size) {
235    // make sure that it is integer.
236    $size=(int) $size;
237
238    $ret='';
239
240    if ($size >= (1024*1024)) {
241        $ret = sprintf(_("%s MB"),round($size/(1024*1024),1));
242    } elseif ($size >= 1024) {
243        $ret = sprintf(_("%s KB"),round($size/1024,1));
244    } else {
245        $ret = sprintf(_("%s B"),$size);
246    }
247    return $ret;
248}
249
250/**
251 * Prints selection boxes in imported data table headers
252 *
253 * Send the field numbers entered in the text boxes by the user back to
254 * this script for more processing
255 * email is handled differently, not being an array
256 * @param integer $csvmax max number of columns
257 * @param integer $column column number
258 */
259function aie_create_Select($csvmax,$column) {
260    // $column is the one that should be selected out of the bunch
261    echo "<select name=\"COL$column\">\n";
262
263    if($column > 5)
264        $column = 5; // So we have only our normal choices.
265
266    for($temp = 0; $temp <= 5; $temp++) {
267        echo "<option value=\"$temp\"";
268        if ($column==$temp)
269            echo " selected";
270        if ($temp == 0)
271            echo '>' . _("Nickname") . "</option>\n";
272        if ($temp == 1)
273            echo '>' . _("First Name") . "</option>\n";
274        if ($temp == 2)
275            echo '>' . _("Last Name") . "</option>\n";
276        if ($temp == 3)
277            echo '>' . _("Email") . "</option>\n";
278        if ($temp == 4)
279            echo '>' . _("Additional Info") . "</option>\n";
280        if ($temp == 5)
281            echo '>' . _("Do Not Include") . "</option>\n";
282    }
283    echo "</select>\n";
284}
285
286/**
287 * @param string $row
288 * @param string $text_delimiter (since 1.0) gets POST parameter to fix
289 *  escaped text delimiters. Works only in PHP 4.3.0+
290 * @param array $csvorder (since 1.0) controls order imported address book fields
291 * @return mixed if string is returned - it contains fatal processing error.
292 *  if array - processed csv data. Array keys should be counted. If count = 1,
293 *  possible processing error.
294 */
295function aie_CSVProcess($row,$text_delimiter, &$csvorder) {
296    global $aie_input_charsets, $default_charset;
297
298    // convert character set
299    if (sqgetGlobalVar('input_charset',$input_charset,SQ_POST) &&
300        function_exists('charset_convert') &&
301        $input_charset != $default_charset &&
302        in_array($input_charset,$aie_input_charsets)) {
303        foreach($row as $key => $value) {
304            $row[$key] = charset_convert($input_charset,$value,$default_charset,false);
305        }
306    }
307
308    // undo escaped text delimiters
309    if (check_php_version(4,3,0)) {
310        foreach($row as $key => $value) {
311            $row[$key] = str_replace('\\' . $text_delimiter, $text_delimiter, $value);
312        }
313    }
314
315    // Make sure that it is not LDIF (use 'objectclass' attribute for detection)
316    if (preg_match("/^objectclass(?:)$/",trim($row[0])) ||
317        preg_match("/^objectclass:.*$/",trim($row[0]))) {
318        return _("LDIF import is not supported.");
319    }
320
321    // detect header row
322    if (preg_grep("/((?:First Name)|(?:Last Name)|(?:E-mail Address))/",$row)) {
323        foreach($row as $key => $value) {
324            if ($value == "First Name" ) {
325                if(isset($csvorder[$key])) {
326                    $csvorder[1] = $csvorder[$key];
327                } else {
328                    $csvorder[1]= $key;
329                }
330            }
331            if ($value == "Last Name" ) {
332                if(isset($csvorder[$key])) {
333                    $csvorder[2] = $csvorder[$key];
334                } else {
335                    $csvorder[2]= $key;
336                }
337            }
338            if ($value == "E-mail Address" ) {
339                if(isset($csvorder[$key])) {
340                    $csvorder[3] = $csvorder[$key];
341                } else {
342                    $csvorder[3]= $key;
343                }
344            }
345        }
346        return array();
347    }
348
349    if (count($csvorder) > 0) {
350        // This is swapping elements to make firstname, last name, and email be in the 1,2,3 spot, respectively
351        foreach($csvorder as $key => $value) {
352            // check if field is set (maybe csv has less fields).
353            $temp = (isset($row[$key]) ? $row[$key] : '');
354            $row[$key] = $row[$value];
355            $row[$value] = $temp;
356        }
357        return $row;
358    }
359    return $row;
360}
361
362/**
363 * fgetcsv wrapper to solve differences between 4.3.0+ and older
364 * @param resource $handle
365 * @param integer $length
366 * @param string $delimiter
367 * @param string $enclosure
368 * @since 1.0
369 */
370function aie_fgetcsv($handle,$length,$delimiter,$enclosure) {
371    if (check_php_version(4,3,0)) {
372        return fgetcsv($handle,$length,$delimiter,$enclosure);
373    } else {
374        return fgetcsv($handle,$length,$delimiter);
375    }
376}
377
378/**
379 * Creates address book selection options
380 *
381 * Tags use 'backend' input field.
382 *
383 * Backend ($v-bname, $v->listing and $v-writeable) specifics:
384 *
385 * local_file - writeable parameter is available. listing parameter
386 *   is available since 1.5.1. Older listing behavior defaults to
387 *   true
388 *
389 * global_file - backend is merged with local_file in 1.4.4 and 1.5.1.
390 *   writeable parameter is available. listing parameter is
391 *   not available and defaults to true.
392 *
393 * database - writeable parameter is available. listing parameter
394 *   is available since 1.4.4 and 1.5.1. Older listing behavior
395 *   defaults to true.
396 *
397 * ldap_server - writeable parameter is not available and backend is
398 *   read only. listing parameter is available since 1.5.1. Older
399 *   listing behavior defaults to false. number of returned results
400 *   can be limited by backend options. backend can be uninitialized
401 *   in some cases.
402 *
403 * Listing is evaluated by list_addr() function behavior. In some cases
404 * backends might allow listing with wide search in search() method, but
405 * such backend behavior is treated as unsupported and might be removed
406 * in some SquirrelMail version.
407 * @param string $listing_type all, write or list
408 * @param integer $backend_count returns number of available backends.
409 *  It allows to detect which forms tags are used. 0 = empty string,
410 *  1 = hidden input, 2 or more = select box
411 *
412 * abook_import_export gettext domain must be initialized before calling
413 * this function, but code can use any domain
414 * @return string html form tags (select or hidden input)
415 * @since 1.0
416 */
417function aie_select_backend($listing_type, &$backend_count) {
418    global $abook;
419
420    // save current gettext domain
421    $current_textdomain=textdomain('');
422
423    if (empty($abook) ||
424        ! is_object($abook) ||
425        strtolower(get_class($abook))!='addressbook') {
426
427        if ($current_textdomain!='squirrelmail') {
428            /**
429             * switch domain. use short switch because plugin
430             * depends on php gettext or 1.5.1 gettext implementation
431             * and short switches work in both
432             */
433            textdomain('squirrelmail');
434        }
435        // init local and remote backends. don't show errors
436        $abook = addressbook_init(false);
437    }
438
439    // address book init failed.
440    if ($abook==false) {
441        // restore domain
442        textdomain($current_textdomain);
443        // inform about backend counter
444        $backend_count = 0;
445        return '';
446    }
447
448    $available_abook = $abook->localbackend;
449    if ( $abook->numbackends > 1 ) {
450        $backends = $abook->get_backend_list();
451
452        while (list($undef,$v) = each($backends)) {
453            switch ($v->bname) {
454            case 'ldap_server':
455                $writing_enabled = (isset($v->writeable) ? $v->writeable : false);
456                $listing_enabled = (isset($v->listing)   ? $v->listing   : false);
457                break;
458            default:
459                $writing_enabled = (isset($v->writeable) ? $v->writeable : true);
460                $listing_enabled = (isset($v->listing)   ? $v->listing   : true);
461            }
462
463            switch ($listing_type) {
464            case 'write':
465                if ($writing_enabled) {
466                    // add each backend to array
467                    $available_abooks[$v->bnum]=$v->sname;
468                    // save backend number
469                    $available_abook=$v->bnum;
470                }
471                break;
472            case 'list':
473                if ($listing_enabled) {
474                    // add each backend to array
475                    $available_abooks[$v->bnum]=$v->sname;
476                    // save backend number
477                    $available_abook=$v->bnum;
478                }
479                break;
480            default:
481                // add each backend to array
482                $available_abooks[$v->bnum]=$v->sname;
483                // save backend number
484                $available_abook=$v->bnum;
485                break;
486            }
487        }
488        if (count($available_abooks)>1) {
489            // restore domain
490            textdomain($current_textdomain);
491            // inform about backend counter
492            $backend_count = count($available_abooks);
493            // we have more than one writeable backend
494            return addSelect('backend',$available_abooks,null,true);
495        }
496    }
497    // restore domain
498    textdomain($current_textdomain);
499    // inform about backend counter
500    $backend_count = 1;
501    // Only one backend exists or is writeable.
502    return addHidden('backend', $available_abook);
503}
504
505/**
506 * Own error message function
507 *
508 * Function provides better controls than internal SquirrelMail
509 * error_box() function.
510 * @param string $error_msg
511 * @param string $error_title
512 * @param boolean $close_html
513 * @since 1.0
514 */
515function aie_error_box($error_msg,$error_title='',$close_html=false) {
516    global $pageheader_sent, $color;
517
518    if ( !isset( $color ) ) {
519        $color = array();
520        $color[0]  = '#dcdcdc';  /* light gray    TitleBar               */
521        $color[1]  = '#800000';  /* red                                  */
522        $color[2]  = '#cc0000';  /* light red     Warning/Error Messages */
523        $color[4]  = '#ffffff';  /* white         Normal Background      */
524        $color[7]  = '#0000cc';  /* blue          Links                  */
525        $color[8]  = '#000000';  /* black         Normal text            */
526        $color[9]  = '#ababab';  /* mid-gray      Darker version of #0   */
527    }
528
529    if (empty($error_title)) $error_title = _("ERROR");
530
531    /* check if the page header has been sent; if not, send it! */
532    if(!isset($pageheader_sent) && !$pageheader_sent) {
533        textdomain('squirrelmail');
534        displayPageHeader($color,'None');
535        $pageheader_sent = true;
536        echo "<body text=\"$color[8]\" bgcolor=\"$color[4]\" link=\"$color[7]\" vlink=\"$color[7]\" alink=\"$color[7]\">\n\n";
537    }
538
539    echo '<table cellpadding="1" cellspacing="0" align="center" border="0" bgcolor="'.$color[9].'">'.
540         '<tr><td>'.
541         '<table width="100%" cellpadding="0" cellspacing="0" align="center" border="0" bgcolor="'.$color[4].'">'.
542         '<tr><td align="center" bgcolor="'.$color[0].'">'.
543         '<font color="'.$color[2].'"><b>' . $error_title . '</b></font>'.
544         '</td></tr><tr><td>'.
545         '<table cellpadding="1" cellspacing="5" align="center" border="0">'.
546         '<tr>' . html_tag( 'td', $error_msg."\n", 'left') . '</tr></table>'.
547         '</td></tr></table></td></tr></table>';
548
549    if ($close_html) {
550        die('</body></html>');
551    } else {
552        // revert domain
553        textdomain('abook_import_export');
554    }
555}
556
557if (! function_exists('dgettext')) {
558    /**
559     * dgettext replacement for broken setups.
560     * @ignore
561     */
562    function dgettext($domain,$str) {
563        return $str;
564    }
565}
566