You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

import.inc 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344
  1. <?php
  2. /**
  3. +-----------------------------------------------------------------------+
  4. | program/steps/addressbook/import.inc |
  5. | |
  6. | This file is part of the Roundcube Webmail client |
  7. | Copyright (C) 2008-2013, The Roundcube Dev Team |
  8. | |
  9. | Licensed under the GNU General Public License version 3 or |
  10. | any later version with exceptions for skins & plugins. |
  11. | See the README file for a full license statement. |
  12. | |
  13. | PURPOSE: |
  14. | Import contacts from a vCard or CSV file |
  15. | |
  16. +-----------------------------------------------------------------------+
  17. | Author: Thomas Bruederli <roundcube@gmail.com> |
  18. | Author: Aleksander Machniak <machniak@kolabsys.com> |
  19. +-----------------------------------------------------------------------+
  20. */
  21. /** The import process **/
  22. $importstep = 'rcmail_import_form';
  23. if (is_array($_FILES['_file'])) {
  24. $replace = (bool)rcube_utils::get_input_value('_replace', rcube_utils::INPUT_GPC);
  25. $target = rcube_utils::get_input_value('_target', rcube_utils::INPUT_GPC);
  26. $with_groups = intval(rcube_utils::get_input_value('_groups', rcube_utils::INPUT_GPC));
  27. $vcards = array();
  28. $upload_error = null;
  29. $CONTACTS = $RCMAIL->get_address_book($target, true);
  30. if (!$CONTACTS->groups) {
  31. $with_groups = false;
  32. }
  33. if ($CONTACTS->readonly) {
  34. $OUTPUT->show_message('addresswriterror', 'error');
  35. }
  36. else {
  37. foreach ((array)$_FILES['_file']['tmp_name'] as $i => $filepath) {
  38. // Process uploaded file if there is no error
  39. $err = $_FILES['_file']['error'][$i];
  40. if ($err) {
  41. $upload_error = $err;
  42. }
  43. else {
  44. $file_content = file_get_contents($filepath);
  45. // let rcube_vcard do the hard work :-)
  46. $vcard_o = new rcube_vcard();
  47. $vcard_o->extend_fieldmap($CONTACTS->vcard_map);
  48. $v_list = $vcard_o->import($file_content);
  49. if (!empty($v_list)) {
  50. $vcards = array_merge($vcards, $v_list);
  51. continue;
  52. }
  53. // no vCards found, try CSV
  54. $csv = new rcube_csv2vcard($_SESSION['language']);
  55. $csv->import($file_content);
  56. $v_list = $csv->export();
  57. if (!empty($v_list)) {
  58. $vcards = array_merge($vcards, $v_list);
  59. }
  60. }
  61. }
  62. }
  63. // no vcards detected
  64. if (!count($vcards)) {
  65. if ($upload_error == UPLOAD_ERR_INI_SIZE || $err == UPLOAD_ERR_FORM_SIZE) {
  66. $size = $RCMAIL->show_bytes(parse_bytes(ini_get('upload_max_filesize')));
  67. $OUTPUT->show_message('filesizeerror', 'error', array('size' => $size));
  68. }
  69. else if ($upload_error) {
  70. $OUTPUT->show_message('fileuploaderror', 'error');
  71. }
  72. else {
  73. $OUTPUT->show_message('importformaterror', 'error');
  74. }
  75. }
  76. else {
  77. $IMPORT_STATS = new stdClass;
  78. $IMPORT_STATS->names = array();
  79. $IMPORT_STATS->skipped_names = array();
  80. $IMPORT_STATS->count = count($vcards);
  81. $IMPORT_STATS->inserted = $IMPORT_STATS->skipped = $IMPORT_STATS->invalid = $IMPORT_STATS->errors = 0;
  82. if ($replace) {
  83. $CONTACTS->delete_all($CONTACTS->groups && $with_groups < 2);
  84. }
  85. if ($with_groups) {
  86. $import_groups = $CONTACTS->list_groups();
  87. }
  88. foreach ($vcards as $vcard) {
  89. $a_record = $vcard->get_assoc();
  90. // Generate contact's display name (must be before validation), the same we do in save.inc
  91. if (empty($a_record['name'])) {
  92. $a_record['name'] = rcube_addressbook::compose_display_name($a_record, true);
  93. // Reset it if equals to email address (from compose_display_name())
  94. if ($a_record['name'] == $a_record['email'][0]) {
  95. $a_record['name'] = '';
  96. }
  97. }
  98. // skip invalid (incomplete) entries
  99. if (!$CONTACTS->validate($a_record, true)) {
  100. $IMPORT_STATS->invalid++;
  101. continue;
  102. }
  103. // We're using UTF8 internally
  104. $email = $vcard->email[0];
  105. $email = rcube_utils::idn_to_utf8($email);
  106. if (!$replace) {
  107. $existing = null;
  108. // compare e-mail address
  109. if ($email) {
  110. $existing = $CONTACTS->search('email', $email, 1, false);
  111. }
  112. // compare display name if email not found
  113. if ((!$existing || !$existing->count) && $vcard->displayname) {
  114. $existing = $CONTACTS->search('name', $vcard->displayname, 1, false);
  115. }
  116. if ($existing && $existing->count) {
  117. $IMPORT_STATS->skipped++;
  118. $IMPORT_STATS->skipped_names[] = $vcard->displayname ?: $email;
  119. continue;
  120. }
  121. }
  122. $a_record['vcard'] = $vcard->export();
  123. $plugin = $RCMAIL->plugins->exec_hook('contact_create',
  124. array('record' => $a_record, 'source' => null));
  125. $a_record = $plugin['record'];
  126. // insert record and send response
  127. if (!$plugin['abort'])
  128. $success = $CONTACTS->insert($a_record);
  129. else
  130. $success = $plugin['result'];
  131. if ($success) {
  132. // assign groups for this contact (if enabled)
  133. if ($with_groups && !empty($a_record['groups'])) {
  134. foreach (explode(',', $a_record['groups'][0]) as $group_name) {
  135. if ($group_id = rcmail_import_group_id($group_name, $CONTACTS, $with_groups == 1, $import_groups)) {
  136. $CONTACTS->add_to_group($group_id, $success);
  137. }
  138. }
  139. }
  140. $IMPORT_STATS->inserted++;
  141. $IMPORT_STATS->names[] = $a_record['name'] ?: $email;
  142. }
  143. else {
  144. $IMPORT_STATS->errors++;
  145. }
  146. }
  147. $importstep = 'rcmail_import_confirm';
  148. }
  149. }
  150. $OUTPUT->set_pagetitle($RCMAIL->gettext('importcontacts'));
  151. $OUTPUT->add_handlers(array(
  152. 'importstep' => $importstep,
  153. 'importnav' => 'rcmail_import_buttons',
  154. ));
  155. // render page
  156. $OUTPUT->send('importcontacts');
  157. /**
  158. * Handler function to display the import/upload form
  159. */
  160. function rcmail_import_form($attrib)
  161. {
  162. global $RCMAIL, $OUTPUT;
  163. $target = rcube_utils::get_input_value('_target', rcube_utils::INPUT_GPC);
  164. $attrib += array('id' => "rcmImportForm");
  165. $writable_books = $RCMAIL->get_address_sources(true, true);
  166. $upload = new html_inputfield(array(
  167. 'type' => 'file',
  168. 'name' => '_file[]',
  169. 'id' => 'rcmimportfile',
  170. 'size' => 40,
  171. 'multiple' => 'multiple',
  172. ));
  173. $form = html::p(null, html::label('rcmimportfile', $RCMAIL->gettext('importfromfile')) . $upload->show());
  174. $table = new html_table(array('cols' => 2));
  175. // addressbook selector
  176. if (count($writable_books) > 1) {
  177. $select = new html_select(array('name' => '_target', 'id' => 'rcmimporttarget', 'is_escaped' => true));
  178. foreach ($writable_books as $book) {
  179. $select->add($book['name'], $book['id']);
  180. }
  181. $table->add('title', html::label('rcmimporttarget', $RCMAIL->gettext('importtarget')));
  182. $table->add(null, $select->show($target));
  183. }
  184. else {
  185. $abook = new html_hiddenfield(array('name' => '_target', 'value' => key($writable_books)));
  186. $form .= $abook->show();
  187. }
  188. // selector for group import options
  189. if (count($writable_books) >= 1 || $writable_books[0]->groups) {
  190. $select = new html_select(array('name' => '_groups', 'id' => 'rcmimportgroups', 'is_escaped' => true));
  191. $select->add($RCMAIL->gettext('none'), '0');
  192. $select->add($RCMAIL->gettext('importgroupsall'), '1');
  193. $select->add($RCMAIL->gettext('importgroupsexisting'), '2');
  194. $table->add('title', html::label('rcmimportgroups', $RCMAIL->gettext('importgroups')));
  195. $table->add(null, $select->show(rcube_utils::get_input_value('_groups', rcube_utils::INPUT_GPC)));
  196. }
  197. // checkbox to replace the entire address book
  198. $check_replace = new html_checkbox(array('name' => '_replace', 'value' => 1, 'id' => 'rcmimportreplace'));
  199. $table->add('title', html::label('rcmimportreplace', $RCMAIL->gettext('importreplace')));
  200. $table->add(null, $check_replace->show(rcube_utils::get_input_value('_replace', rcube_utils::INPUT_GPC)));
  201. $form .= $table->show(array('id' => null) + $attrib);
  202. $OUTPUT->set_env('writable_source', !empty($writable_books));
  203. $OUTPUT->add_label('selectimportfile','importwait');
  204. $OUTPUT->add_gui_object('importform', $attrib['id']);
  205. $out = html::p(null, rcube::Q($RCMAIL->gettext('importdesc'), 'show'))
  206. . $OUTPUT->form_tag(array(
  207. 'action' => $RCMAIL->url('import'),
  208. 'method' => 'post',
  209. 'enctype' => 'multipart/form-data') + $attrib,
  210. $form);
  211. return $out;
  212. }
  213. /**
  214. * Render the confirmation page for the import process
  215. */
  216. function rcmail_import_confirm($attrib)
  217. {
  218. global $IMPORT_STATS, $RCMAIL;
  219. $vars = get_object_vars($IMPORT_STATS);
  220. $vars['names'] = $vars['skipped_names'] = '';
  221. $content = html::p(null, $RCMAIL->gettext(array(
  222. 'name' => 'importconfirm',
  223. 'nr' => $IMPORT_STATS->inserted,
  224. 'vars' => $vars,
  225. )) . ($IMPORT_STATS->names ? ':' : '.'));
  226. if ($IMPORT_STATS->names) {
  227. $content .= html::p('em', join(', ', array_map(array('rcube', 'Q'), $IMPORT_STATS->names)));
  228. }
  229. if ($IMPORT_STATS->skipped) {
  230. $content .= html::p(null, $RCMAIL->gettext(array(
  231. 'name' => 'importconfirmskipped',
  232. 'nr' => $IMPORT_STATS->skipped,
  233. 'vars' => $vars,
  234. )) . ':')
  235. . html::p('em', join(', ', array_map(array('rcube', 'Q'), $IMPORT_STATS->skipped_names)));
  236. }
  237. return html::div($attrib, $content);
  238. }
  239. /**
  240. * Create navigation buttons for the current import step
  241. */
  242. function rcmail_import_buttons($attrib)
  243. {
  244. global $IMPORT_STATS, $OUTPUT;
  245. $target = rcube_utils::get_input_value('_target', rcube_utils::INPUT_GPC);
  246. $attrib += array('type' => 'input');
  247. unset($attrib['name']);
  248. if (is_object($IMPORT_STATS)) {
  249. $attrib['class'] = trim($attrib['class'] . ' mainaction');
  250. $out = $OUTPUT->button(array('command' => 'list', 'prop' => $target, 'label' => 'done') + $attrib);
  251. }
  252. else {
  253. $cancel = $OUTPUT->button(array('command' => 'list', 'label' => 'cancel') + $attrib);
  254. $attrib['class'] = trim($attrib['class'] . ' mainaction');
  255. $out = $OUTPUT->button(array('command' => 'import', 'label' => 'import') + $attrib);
  256. $out .= '&nbsp;';
  257. $out .= $cancel;
  258. }
  259. return $out;
  260. }
  261. /**
  262. * Returns the matching group id. If group doesn't exist, it'll be created if allowed.
  263. */
  264. function rcmail_import_group_id($group_name, $CONTACTS, $create, &$import_groups)
  265. {
  266. $group_id = 0;
  267. foreach ($import_groups as $group) {
  268. if (strtolower($group['name']) == strtolower($group_name)) {
  269. $group_id = $group['ID'];
  270. break;
  271. }
  272. }
  273. // create a new group
  274. if (!$group_id && $create) {
  275. $new_group = $CONTACTS->create_group($group_name);
  276. if (!$new_group['ID'])
  277. $new_group['ID'] = $new_group['id'];
  278. $import_groups[] = $new_group;
  279. $group_id = $new_group['ID'];
  280. }
  281. return $group_id;
  282. }