basename($filename),
'filepath' => drupal_realpath($filename),
'filesize' => filesize($filename),
);
}
return $messages;
}
/**
* Prepare the export of a vocabulary.
*
* @note
* If not used in a form, don't forget to use batch_process().
*
* @param $options
* Array. Same as taxonomy_csv_export.
*
* @return
* Array of errors or nothing (batch process to execute).
*/
function taxonomy_csv_vocabulary_export($options) {
// Check options and return array of messages in case of errors.
if ($options['check_options']) {
// Invoke export admin file.
$module_dir = drupal_get_path('module', 'taxonomy_csv');
require_once("$module_dir/export/taxonomy_csv.export.admin.inc");
$result = _taxonomy_csv_export_check_options($options);
if (count($result)) {
return $result;
}
}
// Complete $options with some csv variables.
$options['separator'] = $options['enclosure'] . $options['delimiter'] . $options['enclosure'];
$line_ending = array(
'Unix' => "\n",
'Mac' => "\r",
'MS-DOS' => "\r\n",
);
$options['end_of_line'] = $line_ending[$options['line_ending']];
// Calculates number of terms to be exported.
$options['total_terms'] = taxonomy_csv_vocabulary_count_terms($options['vocabulary_id']);
// Get infos about fields of vocabulary.
$options['vocabulary'] = array();
foreach ($options['vocabulary_id'] as $vocabulary_id) {
$options['vocabulary'][$vocabulary_id] = taxonomy_vocabulary_load($vocabulary_id);
$vocabulary = &$options['vocabulary'][$vocabulary_id];
$vocabulary->instances = field_info_instances('taxonomy_term', $vocabulary->machine_name);
// Prepare list of fields to be exported.
$vocabulary->fields = array();
// Not included, because referenced terms are exported by name.
// $vocabulary->fields['tid'] = array('cardinality' => 1);
$vocabulary->fields['name'] = array('cardinality' => 1);
// Not included, because there is already 'vocabulary_machine_name'.
// $vocabulary->fields['vid'] = array('cardinality' => 1);
$vocabulary->fields['vocabulary_machine_name'] = array('cardinality' => 1);
$vocabulary->fields['description'] = array('cardinality' => 1);
$vocabulary->fields['format'] = array('cardinality' => 1);
$vocabulary->fields['weight'] = array('cardinality' => 1);
$vocabulary->fields['parent'] = array('cardinality' => -1);
if (module_exists('i18n_taxonomy')) {
switch ($vocabulary->i18n_mode) {
case I18N_MODE_LANGUAGE:
case I18N_MODE_LOCALIZE:
$vocabulary->fields['language'] = array('cardinality' => 1);
break;
case I18N_MODE_TRANSLATE:
case I18N_MODE_MULTIPLE:
$vocabulary->fields['language'] = array('cardinality' => 1);
$vocabulary->fields['i18n_tsid'] = array('cardinality' => 1);
break;
}
}
// @todo
// $vocabulary->fields['guid'] = array('cardinality' => 1);
// Prepare list of unlimited fields to be exported.
$vocabulary->fields_unlimited = array();
$vocabulary->fields_unlimited['parent'] = 1;
if (is_array($vocabulary->instances)) {
foreach ($vocabulary->instances as $field_name => $value) {
$vocabulary->fields[$field_name] = field_info_field($field_name);
// Get the list of fields with an unlimited number of values to avoid
// the loop of check (used with custom fields export).
// The minimum is set to one to avoid zero value.
if ($vocabulary->fields[$field_name]['cardinality'] == -1) {
$vocabulary->fields_unlimited[$field_name] = 1;
}
}
}
}
// Prepare export batch.
$batch = array(
'title' => t('Exporting !total_terms terms to CSV file...', array('!total_terms' => $options['total_terms'])),
'init_message' => t('Starting downloading of datas...') . '
'
. t('Wait some seconds for pre-processing...'),
'progress_message' => '',
'error_message' => t('An error occurred during the export.'),
'finished' => '_taxonomy_csv_vocabulary_export_finished',
'file' => drupal_get_path('module', 'taxonomy_csv') . '/export/taxonomy_csv.export.api.inc',
'progressive' => TRUE,
'operations' => array(
0 => array('_taxonomy_csv_vocabulary_export_process', array($options)),
),
);
batch_set($batch);
}
/**
* Batch process of vocabulary export.
*
* @todo Check if direct query and only tid save is really less memory
* intensive (with core taxonomy cache or not).
* @todo Don't remember terms, but load them one by one in order to decrease
* memory usage.
*
* @param $options
* Array of batch options.
* @param &$context
* Batch context to keep results and messages.
*
* @return
* NULL because use of &$context.
*/
function _taxonomy_csv_vocabulary_export_process($options, &$context) {
// First callback.
if (empty($context['sandbox'])) {
// Remember options as batch_set can't use form_storage.
// It allows too that first line in result is numbered 1 and not 0.
$context['results'][0] = $options;
// Initialize some variables.
$context['results'][0]['current_term'] = 0;
$context['results'][0]['current_name'] = '';
$context['results'][0]['worst_tid'] = 0;
$context['results'][0]['worst_term'] = 0;
$context['results'][0]['worst_name'] = '';
$context['results'][0]['worst_message'] = 799;
// No pointer because new line is appended to file.
$context['results'][0]['handle'] = fopen($options['file']->filepath, 'a+');
// Fetch terms to be exported and order them (by tree or by specific order).
// Prepare a hierarchical tree of terms.
if (in_array($options['export_format'], array(
TAXONOMY_CSV_FORMAT_STRUCTURE,
TAXONOMY_CSV_FORMAT_TREE,
TAXONOMY_CSV_FORMAT_POLYHIERARCHY,
))) {
$context['sandbox']['terms'] = array();
// Export only selected vocabularies.
if ($options['vocabulary_id'] && ($options['vocabulary_id'] != array(0))) {
$vocabularies = array();
foreach ($options['vocabulary_id'] as $vocabulary) {
$vocabularies[] = taxonomy_vocabulary_load($vocabulary);
}
}
// Export all vocabularies.
else {
$vocabularies = taxonomy_vocabulary_get_names();
}
foreach ($vocabularies as $vocabulary) {
$context['sandbox']['terms'] = array_merge($context['sandbox']['terms'], taxonomy_get_tree($vocabulary->vid));
}
}
// Prepare a list of normal terms.
// @todo Prepare a simple list of term names or definitions without links in
// order to reduce memory usage for some formats.
// @todo Use a one term approach to reduce memory usage (anyway, all terms
// will be load)?
else {
$query = new EntityFieldQuery();
$query->entityCondition('entity_type', 'taxonomy_term');
if ($options['vocabulary_id'] && ($options['vocabulary_id'] != array(0))) {
$query->propertyCondition('vid', $options['vocabulary_id']);
}
$query->propertyOrderBy($options['order'], 'ASC');
$terms = $query->execute();
if ($terms['taxonomy_term'] === NULL) {
$terms['taxonomy_term'] = array();
}
$context['sandbox']['terms'] = taxonomy_term_load_multiple(array_keys($terms['taxonomy_term']));
}
// Get max number of values for fields with a undefined number of values.
if ($options['export_format'] == TAXONOMY_CSV_FORMAT_FIELDS) {
// Currently, manage only undefined language.
$language = 'und';
foreach ($context['sandbox']['terms'] as $term) {
foreach (array_keys($options['vocabulary'][$term->vid]->fields_unlimited) as $field_name) {
if (isset($term->{$field_name})) {
// Remember the number of items if this term field has got more
// items than previous one.
if ($field_name == 'parent'
&& (count($term->parent) > $options['vocabulary'][$term->vid]->fields_unlimited['parent'])
) {
$options['vocabulary'][$term->vid]->fields_unlimited['parent'] = count($term->parent);
}
elseif (count($term->{$field_name}[$language]) > $options['vocabulary'][$term->vid]->fields_unlimited[$field_name]) {
$options['vocabulary'][$term->vid]->fields_unlimited[$field_name] = count($term->{$field_name}[$language]);
}
}
}
}
// Keep updated the list of vocabulary with undefined fields.
$context['results'][0]['vocabulary'] = $options['vocabulary'];
$context['sandbox']['vocabulary'] = $options['vocabulary'];
}
// Display a warning if terms count in taxonomy_term_data table is different
// of terms counted with Drupal taxonomy_term_load_multiple() function.
// See http://drupal.org/node/1359260.
if ($options['total_terms'] != count($context['sandbox']['terms'])) {
// Memorize the first wrong term.
$first_wrong_term = array_shift(array_diff_key($terms['taxonomy_term'], taxonomy_term_load_multiple(array_keys($terms['taxonomy_term']))));
$context['results'][0]['worst_tid'] = $first_wrong_term->tid;
$context['results'][0]['worst_term'] = '';
$context['results'][0]['worst_name'] = 'tid:' . ' ' . $first_wrong_term->tid;
$context['results'][0]['worst_message'] = 408; // Warning not a good term.
}
// Clean memory.
if (isset($terms)) {
unset($terms);
}
// Duplicate names will be searched after export process.
$context['results'][0]['duplicate_terms'] = array();
}
elseif (!is_resource($context['results'][0]['handle'])) {
// Reopen file in case of memory out.
$context['results'][0]['handle'] = fopen($options['file']->filepath, 'a+');
}
// Get list of unlimited multivalued fields.
// @todo Currently, these fields are not saved in batch.
if ($options['export_format'] == TAXONOMY_CSV_FORMAT_FIELDS) {
$options['vocabulary'] = $context['sandbox']['vocabulary'];
}
// To simplify use of variables.
$worst_tid = &$context['results'][0]['worst_tid'];
$worst_term = &$context['results'][0]['worst_term'];
$worst_name = &$context['results'][0]['worst_name'];
$worst_message = &$context['results'][0]['worst_message'];
$handle = &$context['results'][0]['handle'];
$duplicate_terms = &$context['results'][0]['duplicate_terms'];
$term_number = &$context['results'][0]['current_term'];
$current_name = &$context['results'][0]['current_name'];
$terms_list = &$context['sandbox']['terms'];
$terms_list_ids = array_keys($terms_list);
// Process one term until end of vocabulary.
if ($term_number < count($context['sandbox']['terms'])) {
$term = $terms_list[$terms_list_ids[$term_number]];
++$term_number;
// Remember current name/tid in case of error.
$current_name = $term->name;
$current_tid = $term->tid;
// Process export of current term.
$result = taxonomy_csv_term_export($term, $options, $terms_list, $duplicate_terms);
// If result is empty, there is nothing to export (with Translation export,
// terms can be skipped and exported with another term).
$result['msg'] = taxonomy_csv_line_export($result['line'], $options, $handle, $result['msg']);
// Remember worst message of exported terms.
$worst_message_new = _taxonomy_csv_worst_message($result['msg']);
if ($worst_message > $worst_message_new) {
$worst_tid = $current_tid;
$worst_term = $term_number;
$worst_name = $current_name;
$worst_message = $worst_message_new;
};
// Remember messages. Currently useless because there isn't any warning or
// notice message (only error). A result level can be added here if needed.
if (count($result['msg'])) {
$context['results'][$term_number] = $result['msg'];
}
// Inform about progress.
$context['message'] = t('Term !term_number of !total_terms processed: %term', array(
'!term_number' => $term_number,
'!total_terms' => $options['total_terms'],
'%term' => $current_name,
));
// Check worst message of exported terms and update progress.
if ($worst_message >= TAXONOMY_CSV_PROCESS_WARNING) {
// Count should be <= 0.99 to avoid batch stop before end (Drupal 7 bug).
$context['finished'] = floor($term_number / count($context['sandbox']['terms']) * 100) / 100;
}
else {
$context['finished'] = 1;
}
}
}
/**
* Callback for finished batch export and display result informations.
*/
function _taxonomy_csv_vocabulary_export_finished($success, $results, $operations) {
$options = &$results[0];
unset($results[0]);
// Close exported file.
if ($options['handle']) {
fclose($options['handle']);
}
// Invoke export stats file if user wants to display results.
if ($options['result_display']) {
$module_dir = drupal_get_path('module', 'taxonomy_csv');
require_once("$module_dir/export/taxonomy_csv.export.result.inc");
}
// Short summary information is different if batch succeeded or not.
if ($success) {
if (!empty($options['worst_tid'])) {
$worst_path = drupal_get_path_alias('taxonomy/term/' . $options['worst_tid']);
$options['worst_name'] = l($options['worst_name'], $worst_path);
}
$variables = array(
'!total_terms' => $options['total_terms'],
'!worst_count' => $options['worst_term'],
'!worst_name' => $options['worst_name'],
'!worst_msg' => $options['result_display'] ?
_taxonomy_csv_message_text($options['worst_message']) :
t('Message code') . ' = ' . $options['worst_message'],
);
$messages = array(
WATCHDOG_DEBUG => t('No error, warnings or notices have been reported during export process of !total_terms terms.', $variables),
WATCHDOG_INFO => t('No error, warnings or notices have been reported during export process of !total_terms terms.', $variables),
WATCHDOG_NOTICE => t('Notices have been reported during export process (bad formatted or empty terms). !total_terms terms processed. First notice occurred on term !worst_count (!worst_name) [!worst_msg].', $variables),
WATCHDOG_WARNING => t('Warnings have been reported during export process (bad formatted terms). !total_terms terms processed. First term skipped is term !worst_count (!worst_name) [!worst_msg].', $variables),
WATCHDOG_ERROR => t('Errors have been reported during export process. Process failed at term !worst_count (!worst_name) of a total of !total_terms [!worst_msg].', $variables),
);
$worst_level = intval($options['worst_message'] / 100);
$message = $messages[$worst_level];
}
else {
$message = t('Exportation failed. Export process was successful until the term !term_count (!term_name) of a total of !total_terms.', array(
'!term_count' => $options['current_term'],
'!term_name' => $options['current_name'],
'!total_terms' => $options['total_terms'],
)) . '
'
. t('This issue is related to export process and may be caused by a memory overrun of the database. If not, you can reinstall module from a fresh release or submit an issue on Taxonomy CSV import/export module.', array(
'!link' => url('http://drupal.org/project/issues/taxonomy_csv/'),
));
$worst_level = WATCHDOG_ERROR;
}
// Set result message in watchdog and eventually in user interface.
// Use of a $message variable is unrecommended, but simpler and working.
// See http://drupal.org/node/323101
watchdog('taxonomy_csv', $message, NULL, $worst_level);
if ($options['result_display']) {
_taxonomy_csv_export_result($options, $worst_level, $message, $results);
}
}
/**
* Export a line.
*
* @param $line
* Array to be exported to a line.
* @param $options
* An associative array of export options:
* - 'separator' : string separator (formatted delimiter and enclosure).
* - 'enclosure' : item enclosure.
* - 'end_of_line': end of line string.
* @param $handle
* Handle of the open file where to save line.
* @param $result_message
* (Optional) Array of messages.
*
* @return
* Result array of messages.
*/
function taxonomy_csv_line_export($line, $options, &$handle, $result = array()) {
if (!$line) {
return $result;
}
// Check if separator, enclosure or line ending exist in line.
$check_line = implode('', $line);
if ((strpos($check_line, $options['separator']) !== FALSE)
|| (($options['enclosure'] != '')
&& (strpos($check_line, $options['enclosure']) !== FALSE))
|| (($options['enclosure'] == '')
&& (strpos($check_line, $options['end_of_line']) !== FALSE))) {
$result[] = 313; // Error delimiter or enclosure.
}
// Skip a term with warning.
elseif (_taxonomy_csv_worst_message($result) < TAXONOMY_CSV_PROCESS_NOTICE) {
}
else {
// Save line to file.
$line = $options['enclosure'] . implode($options['separator'], $line) . $options['enclosure'] . $options['end_of_line'];
if (fwrite($handle, $line) === FALSE) {
$result[] = 312; // Unable to write to file.
}
}
return $result;
}
/**
* Export a term to a line matching the options.
*
* @param $term
* Full term object to export.
* @param $options
* An associative array of export options:
* - export_format : format of the csv line (see taxonomy.api.inc)
* @param $terms_list
* (Optional) Array of all term objects to export, used to avoid to repeat
* fetch of terms.
* @param $duplicate_terms
* (Optional) Array of duplicate terms names indexed by tid.
*
* @return
* Result array with:
* - 'line' => array of exported items,
* - 'msg' => array of messages arrays.
*/
function taxonomy_csv_term_export($term, $options, &$terms_list = array(), $duplicate_terms = array()) {
// Define default values.
$result = array(
'line' => array(),
'msg' => array(),
);
// Only count check because term and options are already checked.
if (count($term)) {
switch ($options['export_format']) {
case TAXONOMY_CSV_FORMAT_FLAT:
$result['line'][] = $term->name;
break;
case TAXONOMY_CSV_FORMAT_STRUCTURE:
case TAXONOMY_CSV_FORMAT_TREE:
$terms = taxonomy_csv_term_get_first_path($term, $terms_list);
foreach ($terms as $parent) {
$result['line'][] = $parent->name;
}
$result['line'][] = $term->name;
break;
case TAXONOMY_CSV_FORMAT_POLYHIERARCHY:
// @todo
// Warning : taxonomy_csv_term_get_first_path() returns only first path.
break;
case TAXONOMY_CSV_FORMAT_FIELDS:
// Currently, manage only undefined language.
$language = 'und';
// Use of field_get_items is slower.
foreach ($options['vocabulary'][$term->vid]->fields as $field_name => $field) {
$count = 0;
// Item is a Field.
if (isset($field['type'])) {
if (isset($term->{$field_name}[$language])) {
// For taxonomy term reference, use name instead of value.
if ($field['type'] == 'taxonomy_term_reference') {
foreach ($term->{$field_name}[$language] as &$item) {
$result['line'][] = isset($terms_list[$item['tid']]) ?
$terms_list[$item['tid']]->name :
taxonomy_term_load($item['tid'])->name;
}
}
// For long text, need to escape the value.
elseif ($field['type'] == 'text_long'
|| $field['type'] == 'text_with_summary'
) {
foreach ($term->{$field_name}[$language] as &$item) {
$result['line'][] = _taxonomy_csv_escape_line_break($item['value']);
}
}
else {
// Key is generally 'value' but it can be something else.
reset($field['columns']);
foreach ($term->{$field_name}[$language] as &$item) {
$result['line'][] = $item[key($field['columns'])];
}
}
$count = count($term->{$field_name}[$language]);
}
}
// Item is a database term field (name, tid, language...).
else {
// For taxonomy term parent, use name instead of value.
if ($field_name == 'parent') {
if (isset($term->parent)) {
foreach ($term->parent as $tid) {
$result['line'][] = isset($terms_list[$tid]) ?
$terms_list[$tid]->name :
taxonomy_term_load($tid)->name;
}
$count = count($term->parent);
}
}
elseif ($field_name == 'description') {
$result['line'][] = _taxonomy_csv_escape_line_break($term->description);
$count = 1;
}
else {
$result['line'][] = $term->{$field_name};
$count = 1;
}
}
// Add empty value the max number of values in the vocabulary times.
if (isset($options['vocabulary'][$term->vid]->fields_unlimited[$field_name])) {
if ($count < $options['vocabulary'][$term->vid]->fields_unlimited[$field_name]) {
$result['line'] = array_merge($result['line'], array_fill(0, $options['vocabulary'][$term->vid]->fields_unlimited[$field_name] - $count, ''));
}
}
elseif ($count < $field['cardinality']) {
$result['line'] = array_merge($result['line'], array_fill(0, $field['cardinality'] - $count, ''));
}
}
break;
case TAXONOMY_CSV_FORMAT_TRANSLATE:
if (!module_exists('i18n_taxonomy')) {
$result['msg'][] = 360; // Translation error.
break;
}
switch ($options['vocabulary'][$term->vid]->i18n_mode) {
case I18N_MODE_NONE:
case I18N_MODE_LANGUAGE:
$result['line'][] = $term->name;
break;
case I18N_MODE_LOCALIZE:
$result['line'][] = $term->name;
$languages = locale_language_list('name');
unset($languages[language_default('language')]);
foreach ($languages as $language => $value) {
$translation = i18n_string_translate(
array('taxonomy', 'term', $term->tid, 'name'),
$term->name,
array('langcode' => $language)
);
$result['line'][] = $translation ?
$translation :
'';
}
break;
case I18N_MODE_TRANSLATE:
case I18N_MODE_MULTIPLE:
$languages = array(
'und' => t('Language neutral'),
);
$languages += locale_language_list('name');
$languages = array_flip(array_keys($languages));
$result['line'] = array_fill(0, count($languages), '');
if ($term->i18n_tsid == 0) {
$result['line'][$languages[$term->language]] = $term->name;
}
else {
$translation_set = i18n_translation_set_load($term->i18n_tsid);
$translations = $translation_set->get_translations();
$language_min = $languages[$term->language];
foreach ($translations as $language => $translated_term) {
$result['line'][$languages[$language]] = $translated_term->name;
// Check if this term is already exported (when there is a term
// with a language before the current one).
if ($languages[$language] < $language_min) {
$language_min = $languages[$language];
}
}
// This term is already exported or will be exported with another
// term.
if ($language_min < $languages[$term->language]) {
$result['line'] = array();
}
}
break;
}
break;
default:
$result['msg'][] = 307; // Error unknown export format.
}
}
else {
$result['msg'][] = 385; // Error no term to process.
}
// Clean result.
$result['msg'] = array_unique($result['msg']);
sort($result['msg']);
return $result;
}