commiting uncommited changes on live site
[weblabels.fsf.org.git] / crm.fsf.org / 20131203 / files / sites / all / modules-new / uif / uif.admin.inc
1 <?php
2
3 /**
4 * @file
5 * Simple, extensible user import from a CSV file.
6 */
7
8 /**
9 * User import multi-part form.
10 */
11 function uif_import_form($form, &$form_state) {
12 // Cause return to beginning if we just completed an import
13 if (isset($form_state['storage']['step']) && $form_state['storage']['step'] >= 3) {
14 unset($form_state['storage']);
15 }
16
17 $step = empty($form_state['storage']['step']) ? 1 : $form_state['storage']['step'];
18 $form_state['storage']['step'] = $step;
19
20 switch ($step) {
21 case 1:
22 $form['instructions'] = array(
23 '#type' => 'fieldset',
24 '#title' => t('User import help'),
25 '#collapsible' => TRUE,
26 '#collapsed' => TRUE,
27 );
28 $form['instructions']['help'] = array(
29 '#markup' => theme('uif_form_help'),
30 );
31 $file_size_msg = t('Your PHP settings limit the maximum file size per upload to %size. Depending on your server environment, these settings may be changed in the system-wide php.ini file, a php.ini file in your Drupal root directory, in your Drupal site\'s settings.php file, or in the .htaccess file in your Drupal root directory.', array('%size' => format_size(file_upload_max_size())));
32
33 $form['user_upload'] = array(
34 '#type' => 'file',
35 '#title' => t('Import file'),
36 '#size' => 40,
37 '#description' => t('Select the CSV file to be imported.') . '<br />' . $file_size_msg,
38 );
39
40 $form['field_delimiter'] = array(
41 '#type' => 'select',
42 '#title' => t('Field delimiter'),
43 '#default_value' => variable_get('uif_field_delimiter', ','),
44 '#options' => uif_field_delimiters(),
45 '#description' => t('Select field delimiter. Comma is typical for CSV export files.'),
46 );
47 $form['value_delimiter'] = array(
48 '#type' => 'select',
49 '#title' => t('Value delimiter'),
50 '#default_value' => variable_get('uif_value_delimiter', '|'),
51 '#options' => uif_value_delimiters(),
52 '#description' => t('Select value delimiter for fields receiving multiple values.'),
53 );
54
55 $preview_count = drupal_map_assoc(array(0, 1, 10, 100, 1000, 10000, 9999999));
56 $preview_count[0] = t('None - just do it');
57 $preview_count[9999999] = t('Preview all');
58
59 $form['preview_count'] = array(
60 '#type' => 'select',
61 '#title' => t('Users to preview'),
62 '#default_value' => 10,
63 '#options' => $preview_count,
64 '#description' => t('Number of users to preview before importing. Note: If you run out of memory set this lower or increase your memory.')
65 );
66
67 $form['notify'] = array(
68 '#type' => 'checkbox',
69 '#title' => t('Notify new users of account'),
70 '#description' => t('If checked, each newly created user will receive the <em>Welcome, new user created by administrator</em> email using the template on the <a href="@url1">user settings page</a>. This is the same email sent for <a href="@url2">admin-created accounts</a>.', array('@url1' => url('admin/user/settings'), '@url2' => url('admin/user/user/create'))),
71 );
72
73 $form['next'] = array(
74 '#type' => 'submit',
75 '#value' => t('Next')
76 );
77
78 // Set form parameters so we can accept file uploads.
79 $form['#attributes'] = array('enctype' => 'multipart/form-data');
80 break;
81
82 case 2:
83 $form['instructions'] = array(
84 '#markup' => t('Preview these records and when ready to import click Import users.'),
85 '#prefix' => '<div id="uif_form_instructions">',
86 '#suffix' => '</div>',
87 );
88 $form['user_preview'] = array(
89 '#markup' => $form_state['storage']['user_preview'],
90 '#prefix' => '<div id="uif_user_preview">',
91 '#suffix' => '</div>',
92 );
93 $form['back'] = array(
94 '#type' => 'submit',
95 '#value' => t('Back'),
96 '#submit' => array('uif_import_form_back'),
97 );
98 $form['submit'] = array(
99 '#type' => 'submit',
100 '#value' => t('Import users'),
101 );
102 break;
103 }
104
105 return $form;
106 }
107
108 /**
109 * Validate the import data.
110 */
111 function uif_import_form_validate($form, &$form_state) {
112 $step = empty($form_state['storage']['step']) ? 1 : $form_state['storage']['step'];
113
114 switch ($step) {
115 case 1:
116 // Validate the upload file
117 $validators = array(
118 'file_validate_extensions' => array('csv'),
119 'file_validate_size' => array(file_upload_max_size()),
120 );
121
122 if ($user_file = file_save_upload('user_upload', $validators)) {
123 $errors = uif_validate_user_file($user_file->uri, $data, $form_state);
124 if (!empty($errors)) {
125 form_set_error('user_upload', '<ul><li>' . implode('</li><li>', $errors) . '</li></ul>');
126 return;
127 }
128 }
129 else {
130 form_set_error('user_upload', t('Cannot save the import file to temporary storage. Please try again.'));
131 return;
132 }
133
134 // Save the validated data to avoid reparsing
135 $form_state['storage']['data'] = $data;
136 break;
137 }
138 }
139
140 /**
141 * Form submission handler.
142 */
143 function uif_import_form_submit($form, &$form_state) {
144 $step = empty($form_state['storage']['step']) ? 1 : $form_state['storage']['step'];
145
146 if (1 == $step) {
147 $form_state['rebuild'] = TRUE;
148 $form_state['storage']['notify'] = isset($form_state['values']['notify']) ? $form_state['values']['notify'] : FALSE;
149 $form_state['storage']['field_delimiter'] = $form_state['values']['field_delimiter'];
150 $form_state['storage']['value_delimiter'] = $form_state['values']['value_delimiter'];
151
152 $preview_count = $form_state['values']['preview_count'];
153 if ($preview_count) {
154 $form_state['storage']['preview_count'] = $preview_count;
155 $form_state['storage']['user_preview'] = theme('uif_preview_users', array('data' => $form_state['storage']['data'], 'limit' => $preview_count));
156 }
157 else {
158 $step = 2;
159 }
160 }
161
162 if (2 == $step) {
163 $form_state['rebuild'] = TRUE;
164 uif_batch_import_users($form_state);
165 }
166
167 $form_state['storage']['step'] = $step + 1;
168 }
169
170 /**
171 * Read the user import file and validate on the way.
172 *
173 * @param $uri
174 * filepath to the user import file
175 * @param $data
176 * returns with array of users
177 * @return
178 * FALSE if no errors found
179 * array of error strings if error found
180 */
181 function uif_validate_user_file($uri, &$data, $form_state) {
182 $data = array();
183 $data['user'] = array();
184 $line = 0;
185 $delimiter = $form_state['values']['field_delimiter'];
186
187 // Without this fgetcsv() fails for Mac-created files
188 ini_set('auto_detect_line_endings', TRUE);
189
190 if ($fp = fopen($uri, 'r')) {
191 // Read the header and allow alterations
192 $header_row = fgetcsv($fp, NULL, $delimiter);
193 $header_row = uif_normalize_header($header_row);
194 uif_adjust_header_values($header_row);
195 drupal_alter('uif_header', $header_row);
196 $line++;
197
198 $errors = module_invoke_all('uif_validate_header', $header_row, $form_state);
199 uif_add_line_number($errors, $line);
200 if (!empty($errors)) {
201 return $errors;
202 }
203 $data['header'] = $header_row;
204
205 // Gather core and entity field info
206 $data['fields'] = uif_get_field_info($header_row);
207
208 // Read the data
209 $errors = array();
210 while (!feof($fp) && (count($errors) < 20)) {
211 // Read a row and allow alterations
212 $row = fgetcsv($fp, NULL, $delimiter);
213 drupal_alter('uif_row', $row, $header_row);
214 $line++;
215
216 if (uif_row_has_data($row)) {
217 $user_row = uif_clean_and_key_row($header_row, $row, $line);
218 $args = array(':mail' => db_like($user_row['mail']));
219 $uid = db_query_range('SELECT uid FROM {users} WHERE mail LIKE :mail', 0, 1, $args)->fetchField();
220
221 $more_errors = module_invoke_all('uif_validate_user', $user_row, $uid, $header_row, $form_state);
222 uif_add_line_number($more_errors, $line);
223 $errors = array_merge($errors, $more_errors);
224 $data['user'][] = $user_row;
225 }
226 }
227
228 // Any errors?
229 if (!empty($errors)) {
230 return $errors;
231 }
232 }
233 else {
234 return t('Cannot open that import file.');
235 }
236
237 // Final validation opportunity after header and all users validated individually.
238 $errors = module_invoke_all('uif_validate_all_users', $data['user'], $form_state);
239 if (!empty($errors)) {
240 return $errors;
241 }
242 }
243
244 /**
245 * Trim all elements of $row, and pad $row out to the number of columns in the
246 * $header. Then replace keys in $row with $header values.
247 */
248 function uif_clean_and_key_row($header, $row, $line) {
249 $row = array_map('trim', $row);
250
251 $raw_row = $row;
252 $row = array_map('uif_clean_value', $row);
253
254 for ($i = 0; $i < count($row); $i++) {
255 if ($raw_row[$i] !== $row[$i]) {
256 $vars = array('@line' => $line, '@column' => $header[$i]);
257 drupal_set_message(t('Warning on row @line: Non UTF-8 characters were removed from @column column.', $vars), 'warning');
258 }
259 }
260
261 if (count($row) < count($header)) {
262 $row = array_merge($row, array_fill(count($row), count($header) - count($row), ''));
263 drupal_set_message(t('Warning on row @line: Empty values added for missing data.', array('@line' => $line)), 'warning');
264 }
265 elseif (count($row) > count($header)) {
266 array_splice($row, count($header));
267 drupal_set_message(t('Warning on row @line: Data values beyond header were truncated.', array('@line' => $line)), 'warning');
268 }
269
270 $row = array_combine($header, $row);
271 return $row;
272 }
273
274 /**
275 * Check that input is UTF-8.
276 */
277 function uif_clean_value($value) {
278 if (!drupal_validate_utf8($value)) {
279 // Remove all chars except LF, CR, and basic ascii
280 return preg_replace('/[^\x0A\x0D\x20-\x7E]/', '', $value);
281 }
282
283 return $value;
284 }
285
286 /**
287 * Is there data in the row?
288 */
289 function uif_row_has_data($row) {
290 if (isset($row) && is_array($row)) {
291 foreach ($row as $value) {
292 $value = trim($value);
293 if (!empty($value)) {
294 return TRUE;
295 }
296 }
297 }
298 return FALSE;
299 }
300
301 /**
302 * Normalize the header columns.
303 */
304 function uif_normalize_header($header) {
305 $header = array_map('trim', $header);
306 $normal_header = array();
307 foreach ($header as $column) {
308 $normal_header[] = strtolower($column);
309 }
310 return $normal_header;
311 }
312
313 /**
314 * Implementation of hook_uif_validate_header().
315 */
316 function uif_uif_validate_header($header) {
317 $errors = array();
318
319 if (!in_array('mail', $header)) {
320 $errors[] = t('There is no mail column in the import file.');
321 }
322
323 $labels = array();
324 foreach ($header as $label) {
325 if (isset($labels[$label])) {
326 $errors[] = t('Repeated columns in input file are not allowed: %label', array('%label' => $label));
327 }
328 $labels[$label] = 1;
329 }
330
331 return $errors;
332 }
333
334 /**
335 * Implementation of hook_uif_validate_user().
336 */
337 function uif_uif_validate_user($user_data, $uid, $header = NULL) {
338 $errors = array();
339
340 if (!valid_email_address($user_data['mail'])) {
341 $errors[] = t('Missing or invalid email address: %mail', array('%mail' => $user_data['mail']));
342 }
343 if (isset($user_data['name']) && empty($user_data['name'])) {
344 $errors[] = t('Username is empty. Leave this column out to create a unique username based on email address.', array());
345 }
346 if (isset($user_data['pass']) && empty($user_data['pass'])) {
347 $errors[] = t('Password is empty. Leave this column out to have an automatically generated password.', array());
348 }
349 if (isset($user_data['roles'])) {
350 uif_parse_roles($user_data['roles'], $roles_errors);
351 $errors = array_merge($errors, $roles_errors);
352 }
353
354 return $errors;
355 }
356
357 /**
358 * Prepend the line number on the error.
359 */
360 function uif_add_line_number(&$errors, $line) {
361 foreach ($errors as &$error) {
362 $error = t('Error on row !line:', array('!line' => $line)) . ' ' . $error;
363 }
364 }
365
366 /**
367 * Return user to starting point on template multi-form.
368 */
369 function uif_import_form_back($form, &$form_state) {
370 $form_state['storage']['step'] = 1;
371 }
372
373 /**
374 * Theme preview of all users.
375 */
376 function theme_uif_preview_users($variables) {
377 $data = $variables['data'];
378 $limit = $variables['limit'];
379 $current = 0;
380 $output = '';
381
382 foreach ($data['user'] as $user_data) {
383 $current++;
384 if ($current > $limit) {
385 break;
386 }
387 $output .= theme('uif_preview_one_user', array('data' => $user_data));
388 }
389
390 if (!$output) {
391 $output = t('There are no users to import.');
392 }
393
394 return $output;
395 }
396
397 /**
398 * Theme preview of a single user.
399 */
400 function theme_uif_preview_one_user($variables) {
401 $user_data = $variables['data'];
402 $rows = array();
403 foreach ($user_data as $field => $value) {
404 $rows[] = array($field, $value);
405 }
406
407 $args = array(':mail' => db_like($user_data['mail']));
408 $user_exists = db_query('SELECT COUNT(*) FROM {users} WHERE mail LIKE :mail', $args)->fetchField();
409 $annotation = $user_exists ? t('update') : t('create');
410 $heading = $user_data['mail'] . ' (' . $annotation . ')';
411
412 return '<h3>' . $heading . '</h3>' . theme('table', array('rows' => $rows));
413 }
414
415 /**
416 * Batch import all users.
417 */
418 function uif_batch_import_users($form_state) {
419 $batch = array(
420 'title' => t('Importing users'),
421 'operations' => array(
422 array('uif_batch_import_users_process', array($form_state))
423 ),
424 'progress_message' => '', // uses count(operations) which is irrelevant in this case
425 'finished' => 'uif_batch_import_users_finished',
426 'file' => drupal_get_path('module', 'uif') . '/uif.admin.inc',
427 );
428 batch_set($batch);
429 }
430
431 /**
432 * User import batch processing.
433 */
434 function uif_batch_import_users_process($form_state, &$context) {
435 // Initialize
436 if (empty($context['sandbox']['progress'])) {
437 $context['sandbox']['progress'] = 0;
438 $context['sandbox']['max'] = count($form_state['storage']['data']['user']);
439 $context['results']['created'] = 0;
440 $context['results']['updated'] = 0;
441 }
442
443 // Process max 20 users at a time
444 $processed = 0;
445 $notify = $form_state['storage']['notify'];
446 while ($context['sandbox']['progress'] < $context['sandbox']['max'] && $processed < 20) {
447 $index = $context['sandbox']['progress'];
448 uif_import_user($form_state['storage']['data']['user'][$index], $notify, $context['results'], $form_state);
449 $context['sandbox']['progress']++;
450 $processed++;
451 }
452
453 // Finished yet?
454 if ($context['sandbox']['progress'] != $context['sandbox']['max']) {
455 $context['finished'] = $context['sandbox']['progress'] / $context['sandbox']['max'];
456 }
457 }
458
459 /**
460 * User import batch completion.
461 */
462 function uif_batch_import_users_finished($success, $results, $operations) {
463 if ($success) {
464 global $user;
465 if (isset($results['self'])) {
466 uif_update_user($results['self'], $user->uid);
467 $results['updated']++;
468 unset($results['self']);
469 }
470 $done = t('User import complete.');
471 $created = $results['created'] ?
472 ' ' . format_plural($results['created'], 'One user was created.', '@count users were created.') . ' ' :
473 '';
474 $updated = $results['updated'] ?
475 ' ' . format_plural($results['updated'], 'One user was updated.', '@count users were updated.') . ' ' :
476 '';
477 $more = t('View the <a href="@url">user list</a>.', array('@url' => url('admin/people')));
478 drupal_set_message($done . $created . $updated . $more);
479 }
480 else {
481 drupal_set_message(t('An error occurred and processing did not complete.'), 'error');
482 }
483 }
484
485 /**
486 * Import one user.
487 */
488 function uif_import_user($user_data, $notify, &$results, $form_state) {
489 $args = array(':mail' => db_like($user_data['mail']));
490 if ($uid = db_query('SELECT uid FROM {users} WHERE mail LIKE :mail', $args)->fetchField()) {
491 global $user;
492 if ($uid === $user->uid) {
493 $results['self'] = $user_data;
494 return;
495 }
496 $account = uif_update_user($user_data, $uid, $form_state);
497 $results['updated']++;
498 }
499 else {
500 $account = uif_create_user($user_data, $notify, $form_state);
501 $results['created']++;
502 }
503 }
504
505 /**
506 * Create a new user.
507 */
508 function uif_create_user($user_data, $notify, $form_state) {
509 $account = array();
510 $account['mail'] = $user_data['mail'];
511 $account['init'] = $user_data['mail'];
512 $account['status'] = 1;
513
514 // Use the provided username if any, or derive it from the email
515 $username = empty($user_data['name']) ? preg_replace('/@.*$/', '', $user_data['mail']) : $user_data['name'];
516 $account['name'] = uif_unique_username($username);
517
518 // Use the provided password if any, otherwise a random one
519 $pass = empty($user_data['pass']) ? user_password() : $user_data['pass'];
520 $account['pass'] = $pass;
521
522 // Add roles if present
523 if (isset($user_data['roles'])) {
524 $account['roles'] = uif_parse_roles($user_data['roles']);
525 }
526
527 $account = array_merge($account, module_invoke_all('uif_pre_create', $account, $user_data, $form_state));
528 $account = user_save('', $account);
529 module_invoke_all('uif_post_create', $account, $user_data, $form_state);
530
531 if ($notify) {
532 $account->password = $pass; // For mail token; _user_mail_notify() expects this
533 _user_mail_notify('register_admin_created', $account);
534 }
535
536 return $account;
537 }
538
539 /**
540 * Update an existing user.
541 */
542 function uif_update_user($user_data, $uid, $form_state) {
543 $account = user_load($uid);
544
545 // todo: Support update of user mail. This requires optional inclusion of uid column,
546 // which would override use of email column as uid lookup method.
547 $changes = module_invoke_all('uif_pre_update', $account, $user_data, $form_state);
548
549 // Update the username if it has changed
550 if (!empty($user_data['name'])) {
551 $username = uif_unique_username($user_data['name'], $uid);
552 if ($username != $account->name) {
553 $changes['name'] = $username;
554 }
555 }
556
557 // Update the password if one is provided
558 if (!empty($user_data['pass'])) {
559 $changes['pass'] = $user_data['pass'];
560 }
561
562 // Update roles if present
563 if (isset($user_data['roles'])) {
564 $changes['roles'] = uif_parse_roles($user_data['roles']);
565 }
566
567 $account = user_save($account, $changes);
568 module_invoke_all('uif_post_update', $account, $user_data, $form_state);
569
570 return $account;
571 }
572
573 /**
574 * Implements hook_uif_pre_create().
575 */
576 function uif_uif_pre_create($account, $user_data, $form_state) {
577 return uif_assign_presave_fields($account, $user_data, $form_state);
578 }
579
580 /**
581 * Implements hook_uif_pre_update().
582 */
583 function uif_uif_pre_update($account, $user_data, $form_state) {
584 return uif_assign_presave_fields($account, $user_data, $form_state);
585 }
586
587 /**
588 * Given a starting point for a Drupal username (e.g. the name portion of an email address) return
589 * a legal, unique Drupal username.
590 *
591 * @param $name
592 * A name from which to base the final user name. May contain illegal characters; these will be stripped.
593 *
594 * @param $uid
595 * (optional) Uid to ignore when searching for unique user (e.g. if we update the username after the
596 * {users} row is inserted)
597 *
598 * @return
599 * A unique user name based on $name.
600 *
601 */
602 function uif_unique_username($name, $uid = 0) {
603 // Strip illegal characters
604 $name = preg_replace('/[^\x{80}-\x{F7} a-zA-Z0-9@_.\'-]/', '', $name);
605
606 // Strip leading and trailing whitespace
607 $name = trim($name);
608
609 // Convert any other series of spaces to a single space
610 $name = preg_replace('/ +/', ' ', $name);
611
612 // If there's nothing left use a default
613 $name = ('' === $name) ? t('user') : $name;
614
615 // Truncate to reasonable size
616 $name = (drupal_strlen($name) > (USERNAME_MAX_LENGTH - 10)) ? drupal_substr($name, 0, USERNAME_MAX_LENGTH - 11) : $name;
617
618 // Iterate until we find a unique name
619 $i = 0;
620 do {
621 $newname = empty($i) ? $name : $name . '_' . $i;
622 $args = array(':uid' => $uid, ':name' => $newname);
623 $found = db_query_range('SELECT uid from {users} WHERE uid <> :uid AND name = :name', 0, 1, $args)->fetchField();
624 $i++;
625 } while ($found);
626
627 return $newname;
628 }
629
630 /**
631 * Theme function for import form help.
632 */
633 function theme_uif_form_help() {
634 $basic_help = '<p>' . t('Choose an import file. You\'ll have a chance to preview the data before doing the import. The import file must have a header row with a name in each column for the value you are importing. Importable fields include the following:') . '</p>';
635 $items = array();
636
637 // Core user table fields
638 $supported_fields = uif_get_supported_fields();
639 foreach ($supported_fields as $name => $data) {
640 if ($data['type'] == 'core') {
641 $required = uif_isset_or($data['required']) ? t('required') : t('optional');
642 $subs = array('@name' => $name, '@required' => $required, '!description' => $data['description']);
643 $items[] = t('@name (@required) - !description', $subs);
644 }
645 }
646
647 // Entity fields
648 foreach (uif_field_info_instances('user', 'user') as $name => $data) {
649 $field_type = uif_lookup_field_type($name);
650 if (uif_is_supported_field($field_type)) {
651 $subs = array(
652 '@name' => $name,
653 '@required' => $data['required'] ? t('required') : t('optional'),
654 '%label' => $data['label'],
655 '%type' => $field_type,
656 '@description' => $data['description'] ? $data['description'] : uif_isset_or($supported_fields[$field_type]['description']),
657 );
658 $items[] = t('@name (@required) - @description (type is %type, human name is %label)', $subs);
659 }
660 }
661
662 $basic_help .= theme('item_list', array('items' => $items));
663
664 if (!module_exists('uif_plus')) {
665 $basic_help .= '<p>' . t('If you need support for entity reference, file, or image fields, or support for modules such profile2 and organic groups, try adding and enabling the <a href="http://drupal.org/project/uif_plus">User Import Framework Plus</a> module.') . '</p>';
666 }
667
668 // Add other modules' help
669 $helps = module_invoke_all('uif_help');
670 array_unshift($helps, $basic_help);
671 $output = '';
672 foreach ($helps as $help) {
673 $output .= '<div class="uif_help_section">' . $help . '</div>';
674 }
675
676 return $output;
677 }
678
679 /**
680 * Field delimiter options.
681 */
682 function uif_field_delimiters() {
683 return array(
684 ',' => ',',
685 ';' => ';',
686 '|' => '|',
687 );
688 }
689
690 /**
691 * Value delimiter options.
692 */
693 function uif_value_delimiters() {
694 return array(
695 '|' => '|',
696 ':' => ':',
697 '_:_' => '_:_',
698 '-*-' => '-*-',
699 );
700 }
701
702 /**
703 * Parse input roles if any.
704 */
705 function uif_parse_roles($roles_string, &$errors = array()) {
706 $roles = array();
707 $errors = array();
708
709 if ($roles_string) {
710 $role_names = explode(variable_get('uif_value_delimiter', '|'), $roles_string);
711 foreach ($role_names as $name) {
712 $rid = db_query('SELECT rid FROM {role} WHERE name = :name', array(':name' => $name))->fetchField();
713 if (is_numeric($rid) && $rid < 2) {
714 $errors[] = t('System-managed roles are not allowed: %name', array('%name' => $name));
715 }
716 elseif ($rid) {
717 $roles[$rid] = $name;
718 }
719 else {
720 $errors[] = t('Unrecognized role: %name', array('%name' => $name));
721 }
722 }
723 }
724
725 return $roles;
726 }
727
728 /**
729 * Perform header adjustments.
730 */
731 function uif_adjust_header_values(&$header_row) {
732 foreach ($header_row as &$label) {
733 if ('email' == $label) {
734 $label = 'mail';
735 drupal_set_message(t('Header label <em>mail</em> substituted for deprecated label <em>email</em>.'), 'warning');
736 }
737 if ('username' == $label) {
738 $label = 'name';
739 drupal_set_message(t('Header label <em>name</em> substituted for deprecated label <em>username</em>.'), 'warning');
740 }
741 if ('password' == $label) {
742 $label = 'pass';
743 drupal_set_message(t('Header label <em>pass</em> substituted for deprecated label <em>password</em>.'), 'warning');
744 }
745 }
746 }
747
748 /**
749 * Prepare core and entity user fields for user_save().
750 */
751 function uif_assign_presave_fields($account, $user_data, $form_state) {
752 $value_delimiter = $form_state['storage']['value_delimiter'];
753 $timestamp_fields = array('created', 'access', 'login');
754 $user_fields = array();
755
756 foreach ($form_state['storage']['data']['fields'] as $label => $info) {
757 if (!$info['supported']) {
758 continue;
759 }
760
761 $parser = uif_isset_or($info['import']['parser']) ? $info['import']['parser'] : 'uif_get_raw_value';
762
763 if ($info['type'] == 'core') {
764 $user_fields[$label] = $parser($account, $info['data'], $user_data[$label]);
765 }
766 elseif ($info['type'] == 'entity') {
767 $field_values = array();
768 $values = explode($value_delimiter, $user_data[$label]);
769 $key = uif_isset_or($info['import']['key']) ? $info['import']['key'] : 'value';
770
771 foreach ($values as $value) {
772 $value = trim($value);
773 if (drupal_strlen($value)) {
774 $parsed_value = $parser($account, $info['data'], $value);
775 if (!is_null($parsed_value)) {
776 $field_values[] = $parsed_value;
777 }
778 }
779 }
780
781 for ($delta = 0; $delta < count($field_values); $delta++) {
782 if (($info['data']['cardinality'] == 1) && ($delta > 0)) {
783 break;
784 }
785
786 if (drupal_strlen($field_values[$delta])) {
787 $user_fields[$label][LANGUAGE_NONE][$delta][$key] = $field_values[$delta];
788 }
789 }
790 }
791 }
792
793 return $user_fields;
794 }
795
796 /**
797 * Read and store field info relevant to the import.
798 */
799 function uif_get_field_info($header) {
800 $field_info = array();
801
802 $users_table = drupal_get_schema('users');
803 $instance_fields = uif_field_info_instances('user', 'user');
804 $supported_fields = uif_get_supported_fields();
805
806 foreach ($header as $label) {
807 if (isset($users_table['fields'][$label])) {
808 $supported = isset($supported_fields[$label]);
809
810 $field_info[$label] = array(
811 'type' => 'core',
812 'supported' => $supported,
813 'data' => $users_table['fields'][$label],
814 );
815
816 if ($supported) {
817 $field_info[$label]['import'] = $supported_fields[$label];
818 }
819 }
820 elseif (isset($instance_fields[$label])) {
821 $data = uif_field_info_field($label);
822 $supported = isset($supported_fields[$data['type']]);
823
824 $field_info[$label] = array(
825 'type' => 'entity',
826 'supported' => $supported,
827 'data' => $data,
828 );
829
830 if ($supported) {
831 $field_info[$label]['import'] = $supported_fields[$data['type']];
832 }
833 }
834 else {
835 // Contrib module handling?
836 $supported = isset($supported_fields[$label]);
837 if (!$supported) {
838 $field_info[$label] = array(
839 'type' => 'unknown',
840 'supported' => FALSE,
841 );
842 }
843 }
844
845 if (!$supported) {
846 drupal_set_message(t('Unknown column @field in the import file. Data in this column will be ignored.', array('@field' => $label)), 'warning');
847 }
848 }
849
850 return $field_info;
851 }
852
853 /**
854 * Given a field's machine name look up the field type.
855 */
856 function uif_lookup_field_type($field_name) {
857 if ($info = uif_field_info_field($field_name)) {
858 return $info['type'];
859 }
860 }
861
862 /**
863 * Return TRUE if a module supports import of the passed field type.
864 */
865 function uif_is_supported_field($field_type) {
866 $supported_fields = uif_get_supported_fields();
867 return isset($supported_fields[$field_type]);
868 }
869
870 /**
871 * Return list of supported fields.
872 */
873 function uif_get_supported_fields() {
874 $supported_fields = &drupal_static(__FUNCTION__);
875 if (!isset($supported_fields)) {
876 $supported_fields = module_invoke_all('uif_supported_fields');
877 drupal_alter('uif_supported_fields', $supported_fields);
878 }
879 return $supported_fields;
880 }
881
882 /**
883 * Implementation of hook_uif_supported_fields().
884 *
885 * Provide out-of-box supported fields.
886 */
887 function uif_uif_supported_fields() {
888 $subs = array(
889 '@strtotime_url' => 'http://php.net/manual/en/function.strtotime.php',
890 '@tz_url' => url('admin/config/regional/settings'),
891 '@mods_url' => url('admin/modules'),
892 '@flds_url' => url('admin/config/people/accounts/fields'),
893 );
894
895 return array(
896 'mail' => array(
897 'type' => 'core',
898 'required' => TRUE,
899 'description' => t('the user\'s email address'),
900 ),
901 'name' => array(
902 'type' => 'core',
903 'description' => t('a name for the user. If not provided, a name is created based on the email.'),
904 ),
905 'pass' => array(
906 'type' => 'core',
907 'description' => t('a password for the user. If not provided, a password is generated.'),
908 ),
909 'roles' => array(
910 'type' => 'core',
911 'description' => t('roles for the user as delimited text, e.g. "admin|editor" (without quotes).'),
912 ),
913 'created' => array(
914 'type' => 'core',
915 'description' => t('the creation date for the user in <a href="@strtotime_url">strtotime()</a> format.', $subs),
916 'parser' => 'uif_get_strtotime_value',
917 ),
918 'access' => array(
919 'type' => 'core',
920 'description' => t('the last access date for the user in <a href="@strtotime_url">strtotime()</a> format.', $subs),
921 'parser' => 'uif_get_strtotime_value',
922 ),
923 'login' => array(
924 'type' => 'core',
925 'description' => t('the last login date for the user in <a href="@strtotime_url">strtotime()</a> format.', $subs),
926 'parser' => 'uif_get_strtotime_value',
927 ),
928 'status' => array(
929 'type' => 'core',
930 'description' => t('the account status (1 = active (default) 0 = blocked).'),
931 ),
932 'timezone' => array(
933 'type' => 'core',
934 'description' => t('the time zone to use for this user. You should <a href="@tz_url">let users set their time zone</a> if you import this.', $subs),
935 ),
936 'language' => array(
937 'type' => 'core',
938 'description' => t('the language to use for this user. You should <a href="@mods_url">enable the locale module</a> if you import this.', $subs),
939 ),
940 'uid' => array(
941 'type' => 'core',
942 'description' => t('the uid of the user (experts only; use mail as unique key, not uid)'),
943 ),
944 'list_boolean' => array(
945 'type' => 'entity',
946 'label' => t('Boolean'),
947 'description' => t('true/false. Use 0 for false, 1 for true.'),
948 ),
949 'number_float' => array(
950 'type' => 'entity',
951 'label' => t('Float'),
952 'description' => t('floating point number'),
953 ),
954 'number_decimal' => array(
955 'type' => 'entity',
956 'label' => t('Decimal'),
957 'description' => t('decimal number'),
958 ),
959 'number_integer' => array(
960 'type' => 'entity',
961 'label' => t('Integer'),
962 'description' => t('integer number'),
963 ),
964 'list_float' => array(
965 'type' => 'entity',
966 'label' => t('List (float)'),
967 'description' => t('list of floating point values'),
968 ),
969 'list_integer' => array(
970 'type' => 'entity',
971 'label' => t('List (integer)'),
972 'description' => t('list of integers'),
973 ),
974 'list_text' => array(
975 'type' => 'entity',
976 'label' => t('List (text)'),
977 'description' => t('list of text values'),
978 ),
979 'text' => array(
980 'type' => 'entity',
981 'label' => t('Text'),
982 'description' => t('text'),
983 ),
984 'text_long' => array(
985 'type' => 'entity',
986 'label' => t('Long text'),
987 'description' => t('long text'),
988 ),
989 'text_with_summary' => array(
990 'type' => 'entity',
991 'label' => t('Long text and summary'),
992 'description' => t('long text and summary'),
993 ),
994 'taxonomy_term_reference' => array(
995 'type' => 'entity',
996 'label' => t('Taxonomy term reference'),
997 'parser' => 'uif_get_taxonomy_value',
998 'key' => 'tid',
999 'description' => t('taxonomy term ID or name'),
1000 ),
1001 );
1002 }
1003
1004 /**
1005 * Helper function to process import data for core supported fields.
1006 */
1007 function uif_get_raw_value($account, $field_info, $value) {
1008 return $value;
1009 }
1010
1011 /**
1012 * Helper function to process import data for core supported fields.
1013 */
1014 function uif_get_taxonomy_value($account, $field_info, $value) {
1015 if (uif_is_natural($value)) {
1016 return $value;
1017 }
1018
1019 if (drupal_strlen($field_info['settings']['allowed_values'][0]['vocabulary'])) {
1020 $terms = taxonomy_get_term_by_name($value, $field_info['settings']['allowed_values'][0]['vocabulary']);
1021 if (count($terms)) {
1022 $tids = array_keys($terms);
1023 return $tids[0];
1024 }
1025 }
1026 }
1027
1028 /**
1029 * Helper function to convert date string to timestamp.
1030 */
1031 function uif_get_strtotime_value($account, $field_info, $value) {
1032 // BUG: do strtotime() check in validation?
1033 $timestamp = strtotime($value);
1034 return ($timestamp < 1) ? strtotime('now') : $timestamp;
1035 }
1036
1037 /**
1038 * Return TRUE if a $val is a natural number (integer 1, 2, 3, ...). Base can
1039 * be changed to zero if desired.
1040 */
1041 function uif_is_natural($val, $base = 1) {
1042 if (!isset($val)) {
1043 return FALSE;
1044 }
1045 $return = ((string)$val === (string)(int)$val);
1046 if ($return && intval($val) < $base) {
1047 $return = FALSE;
1048 }
1049 return $return;
1050 }
1051
1052 /**
1053 * Check if a variable is set and return it if so, otherwise the alternative.
1054 */
1055 function uif_isset_or(&$val, $alternate = NULL) {
1056 return isset($val) ? $val : $alternate;
1057 }
1058
1059 /**
1060 * Wrapper for field_info_instances() to avoid module dependency.
1061 */
1062 function uif_field_info_instances($entity_type = NULL, $bundle_name = NULL) {
1063 return function_exists('field_info_instances') ? field_info_instances($entity_type, $bundle_name) : array();
1064 }
1065
1066 /**
1067 * Wrapper for field_info_field() to avoid module dependency.
1068 */
1069 function uif_field_info_field($field_name) {
1070 return function_exists('field_info_field') ? field_info_field($field_name) : array();
1071 }