'. t('Use this form to import taxonomy terms into a vocabulary from a CSV file.'). '
'; $output .= ''. t('Warning: If you want to update an existing vocabulary, make sure you have a backup before you proceed so you can roll back, if necessary.'). theme('more_help_link', url('admin/help/taxonomy_csv')); return $output; case 'admin/help#taxonomy_csv': $output = '
'. t('This module allows you to import taxonomy terms into a vocabulary from a CSV file.', array('!import-url' => url('admin/content/taxonomy/csv'))). '
'; $output .= ''. t('If you are unsure how to create a CSV file, you might want to use OpenOffice Calc or another spreadsheet application to export your data into a CSV file.'). '
'; $output .= ''. t('You can choose how source will be imported and what existing terms will become.') .'
'; $output .= ''. t('Source') .'
'; $output .= ''. t('Source can be configured with the first field set. The term name will be imported from the first column. You can specify how additional columns should be imported:') .'
'; $output .= 'Animal,Mammal,Dog.'). 'Animal,Mammal,Human, but not Mammal,Human, because in this second case, Mammal is imported as a first level term and not as a Animal term child as in previous line.'). '';
$output .= 'Thesaurus,Taxonomy,Ontology.'). ''. t('Destination') .'
'; $output .= ''. t('Destination can be configured with the second field set. You can specify what will become existing terms. Three choices are possible:'). '
'; $output .= ''. t('When you want to import child term names as well as descriptions, synonyms, related terms and term weights, you should begin with the file containing the hierarchical structure of parent and child. So first import child term names with the Child term names option. Second, upload the other files with the adequate option and one of the Update terms... option.'). '
'; $output .= ''. t('It is recommended to protect terms with quotation marks ("), specialy if they contain non-ASCII letters: "term 1","term 2","term 3".'). '
'. t('Another Drupal module allows CSV import too, despite its name: taxonomy XML. Its approach is different. It uses one file complient to thesauri standard ISO 2788, i.e. a three columns csv file: first term, type of link, second term, or, for specialists, subject, predicate, object. So choose the module best matching your needs.').'
', '#suffix' => '
', '#value' => t('None vocabulary has been found. You need one in order to import your terms into. So you need to !add-new-vocab before to use this module.', array('!add-new-vocab' => l(t('add a new vocabulary'), 'admin/content/taxonomy/add/vocabulary', array('query' => drupal_get_destination())))), ); } else { $form = array('#attributes' => array('enctype' => 'multipart/form-data')); $form['source'] = array('#type' => 'fieldset', '#title' => t('Source')); $form['source']['upload'] = array( '#type' => 'file', '#title' => t('CSV file'), ); if ($max_size = _taxonomy_csv_parse_size(ini_get('upload_max_filesize'))) { $form['source']['upload']['#description'] = t('Due to server restrictions, the maximum upload file size is !size. Files that exceed this size will be disregarded without notice.', array('!size' => format_size($max_size))); } $form['source']['delimiter'] = array( '#type' => 'radios', '#title' => t('CSV file delimiter'), '#options' => array( TAXONOMY_CSV_COMMA => t('Comma " , "'), TAXONOMY_CSV_SEMICOLON => t('Semicolon " ; "'), ), '#default_value' => TAXONOMY_CSV_COMMA, '#description' => t('Choose the delimiter used in the CSV file you want to import.'), ); $form['source']['columns'] = array( '#type' => 'radios', '#title' => t('Additional columns'), '#options' => array( TAXONOMY_CSV_IGNORE => t('Ignore'), TAXONOMY_CSV_CHILDREN => t('Child term names'), TAXONOMY_CSV_FIELDS => t('Term description, term synonyms (may be empty)'), TAXONOMY_CSV_RELATIONS => t('Related terms'), TAXONOMY_CSV_WEIGHTS => t('Term weights'), ), '#default_value' => TAXONOMY_CSV_IGNORE, '#description' => t('The first column is always imported as the term name. This option determines how additional columns will be imported. It will be ignored if your CSV file only contains one column.'), ); $form['source']['advanced'] = array('#type' => 'fieldset', '#title' => t('Advanced settings')); $form['source']['advanced']['disable_convert_to_utf8'] = array( '#type' => 'checkbox', '#title' => t('Disable file conversion because the csv file is UTF-8 encoded'), '#default_value' => TAXONOMY_CSV_DISABLE_CONVERT_TO_UTF8, '#description' => t('This checkbox disables the conversion of the CSV file to UTF-8, what can be a cause of problems with some rare server configurations. Be sure your file is UTF-8 encoded when using this option.'), ); $form['dest'] = array('#type' => 'fieldset', '#title' => t('Destination')); $form['dest']['vid'] = array( '#type' => 'select', '#title' => t('Vocabulary'), '#options' => array(), '#required' => TRUE, '#description' => t('The vocabulary you want to import the file into. You might want to !add-new-vocab.', array('!add-new-vocab' => l(t('add a new vocabulary'), 'admin/content/taxonomy/add/vocabulary', array('query' => drupal_get_destination())))), ); foreach ($vocabularies as $vid => $vocabulary) { $form['dest']['vid']['#options'][$vid] = $vocabulary->name; } $form['dest']['update'] = array( '#type' => 'radios', '#title' => t('Existing terms'), '#options' => array( TAXONOMY_CSV_UPDATE_MERGE => t('Update terms and merge synonyms and related terms'), TAXONOMY_CSV_UPDATE_REPLACE => t('Update terms, but replace existing synonyms and related terms'), TAXONOMY_CSV_CREATE_NEW => t('Ignore current terms and create new ones'), ), '#default_value' => TAXONOMY_CSV_UPDATE_REPLACE, '#description' => t('Whether existing terms with the same name should be updated or ignored.'), ); $form['submit'] = array('#type' => 'submit', '#value' => t('Import')); } return $form; } /** * Parses PHP configuration size values into bytes. * * Edited from an example at http://php.net/manual/en/function.ini-get.php */ function _taxonomy_csv_parse_size($value) { $value = trim($value); $number = (int)substr($value, 0, -1); $suffix = strtoupper(substr($value, -1)); switch ($suffix) { case 'T': $number *= 1024; case 'G': $number *= 1024; case 'M': $number *= 1024; case 'K': $number *= 1024; break; default: $number = $value; } return $number; } /** * Handles CSV import form validation. */ function taxonomy_csv_import_validate($form, &$form_state) { $form_state['upload_file'] = file_save_upload('upload'); if (!$form_state['upload_file']) { form_set_error('upload', t('Please upload a file.')); } } /** * Handles CSV import form submission. */ function taxonomy_csv_import_submit($form, &$form_state) { $options = $form_state['values']; $file = $form_state['upload_file']; if ($options['delimiter'] == TAXONOMY_CSV_SEMICOLON) { $delimiter = ';'; } else { $delimiter = ','; } $options['vocabulary'] = taxonomy_vocabulary_load($options['vid']); // Automatically detect line endings. ini_set('auto_detect_line_endings', '1'); $handle = fopen($file->filepath, 'r'); $batch = array( 'operations' => array(), 'finished' => 'taxonomy_csv_import_finished', 'title' => t('Importing terms from CSV file'), 'init_message' => t('Starting import...'), 'progress_message' => t('Imported @current out of @total lines.'), 'error_message' => t('An error occurred during the import.'), ); $first = TRUE; while ($line = fgetcsv($handle, 4096, $delimiter)) { if (empty($line) || (count($line) == 1 && $line[0] == NULL)) continue; // Skip UTF-8 byte order mark. if ($first) { if (strncmp($line[0], "\xEF\xBB\xBF", 3) === 0) $line[0] = substr($line[0], 3); $first = FALSE; } // Encode the line in base64 to prevent batch errors for weird encodings. $batch['operations'][] = array('taxonomy_csv_import_line', array(array_map('base64_encode', $line), $options)); } fclose($handle); batch_set($batch); } /** * Callback for finished batch import. */ function taxonomy_csv_import_finished($success, $results, $operations) { drupal_set_message(t('The CSV file has been imported.')); } function taxonomy_csv_import_line($line, $options) { // Decode the line. $line = array_map('base64_decode', $line); // Convert line to UTF-8. // no convert if it's already utf-8 : corrects bug http://drupal.org/node/364832 if (!$options['disable_convert_to_utf8']) { $line = array_map('_taxonomy_csv_import_line_to_utf8', $line); } switch ($options['columns']) { case TAXONOMY_CSV_CHILDREN : //first column : root ancestor $parent = 0; for ($c = 0; $c < count($line); $c++) { if (!empty($line[$c])) { $term = array( 'name' => $line[$c], 'vid' => $options['vid'], 'parent' => $parent, ); // Parent terms (so all terms but the last on this line) are always updated. $term = taxonomy_csv_import_term($term, $options['update'] || $c < count($line) - 1); $parent = $term['tid']; } } break; case TAXONOMY_CSV_FIELDS : if (!empty($line[0])) { $term = array( 'name' => $line[0], 'vid' => $options['vid'], ); if (count($line) > 1 && !empty($line[1])) { $term['description'] = $line[1]; } if (count($line) > 2) { $term['synonyms'] = array_unique(array_filter(array_slice($line, 2))); } taxonomy_csv_import_term($term, $options['update']); } break; case TAXONOMY_CSV_RELATIONS : if (!empty($line[0])) { $term = array( 'name' => $line[0], 'vid' => $options['vid'], ); // each related term must exist before it can be related (need of its tid) $relateds = array_unique(array_filter(array_slice($line, 1))); foreach ($relateds as $key => $related_name) { $related_term = array( 'name' => $related_name, 'vid' => $options['vid'], ); $related_term = taxonomy_csv_import_term($related_term, $options['update']); $term['relations'][] = $related_term['tid']; } taxonomy_csv_import_term($term, $options['update']); } break; case TAXONOMY_CSV_WEIGHTS : if (!empty($line[0])) { $term = array( 'name' => $line[0], 'vid' => $options['vid'], ); // needed to be able to set a 0 value for a weight if (empty($line[1])) { $term['weight'] = 0; } else { $term['weight'] = $line[1]; } taxonomy_csv_import_term($term, $options['update']); } break; default : if (!empty($line[0])) { $term = array( 'name' => $line[0], 'vid' => $options['vid'], ); taxonomy_csv_import_term($term, $options['update']); } } } /** * Helper function to convert each line item to UTF-8. */ function _taxonomy_csv_import_line_to_utf8($value) { $enc = mb_detect_encoding($value, "UTF-8, ISO-8859-1, ISO-8859-15", TRUE); if ($enc != "UTF-8") { $value = drupal_convert_to_utf8($value, $enc); } return $value; } /** * Get or create a term with the given name in the given vocabulary and given parent. */ function taxonomy_csv_import_term($term, $update = TAXONOMY_CSV_UPDATE_REPLACE) { if (!empty($term)) { if ($update <> TAXONOMY_CSV_CREATE_NEW) { if ($existing_term = taxonomy_csv_find_term($term['name'], $term['vid'], isset($term['parent']) ? $term['parent'] : NULL)) { switch ($update) { case TAXONOMY_CSV_UPDATE_MERGE : if (isset($term['description'])) ($existing_term['description'] = $term['description']); if (isset($term['weight'])) ($existing_term['weight'] = $term['weight']); if (isset($term['synonyms'])) ($existing_term['synonyms'] = array_unique(array_merge($existing_term['synonyms'], $term['synonyms']))); if (isset($term['relations'])) ($existing_term['relations'] = array_unique(array_merge($existing_term['relations'], $term['relations']))); break; case TAXONOMY_CSV_UPDATE_REPLACE : foreach (array('description', 'weight', 'synonyms', 'relations') as $key) { if (array_key_exists($key, $term)) { $existing_term[$key] = $term[$key]; } } // remove existing description even if there is no new description: needed to set a 0 value // no problem with weight: new always replaces old because the import file contains only two columns if (!empty($term['synonyms']) && empty($term['description'])) { $existing_term['description'] = ''; } } $term = $existing_term; } } // Drupal taxonomy_save_term use a text area format for synonyms if (isset($term['synonyms'])) { $synonyms = $term['synonyms']; $term['synonyms'] = implode("\n", $term['synonyms']); } taxonomy_save_term($term); // keep synonyms as an array if (isset($synonyms)) ($term['synonyms'] = $synonyms); return $term; } } /** * Find, by its name, the first existing term in a given vocabulary and a given parent. */ function taxonomy_csv_find_term($name, $vid, $parent = NULL) { $name = drupal_strtolower(trim($name)); $sql = "SELECT t.tid, t.*, h.parent FROM {term_data} t INNER JOIN {term_hierarchy} h ON t.tid = h.tid WHERE '%s' LIKE LOWER(t.name) AND t.vid = %d "; $args = array($name, $vid); if (!is_null($parent)) { $sql .= "AND h.parent = %d "; $args[] = $parent; } $sql .= "LIMIT 1 "; $result = db_query($sql, $args); $term = db_fetch_array($result); if (isset($term['tid'])) { $term['synonyms'] = taxonomy_get_synonyms($term['tid']); $term['relations'] = _taxonomy_csv_get_related_ids($term['tid']); } else { $term = array(); } return $term; } /** * Make an array of all term IDs related to a given term ID */ function _taxonomy_csv_get_related_ids($tid) { $related_ids = array(); if ($tid) { if ($related_terms = taxonomy_get_related($tid)) { foreach($related_terms as $term => $item) { $related_ids[] = $related_terms[$term]->tid; } } } return $related_ids; } ?>