You are here

taxonomy_csv.export.api.inc in Taxonomy CSV import/export 6.3

Validate export options and manage export process.

File

export/taxonomy_csv.export.api.inc
View source
<?php

/**
 * @file
 * Validate export options and manage export process.
 */

/**
 * Invoke associated include file.
 * taxonomy_csv.export.admin.inc is invoked only if user wants to check input
 * options.
 * taxonomy_csv.export.result.inc is invoked only if user wants result messages.
 */
$module_dir = drupal_get_path('module', 'taxonomy_csv');
require_once "{$module_dir}/taxonomy_csv.api.inc";
require_once "{$module_dir}/taxonomy_csv.term.api.inc";

/**
 * Process the export of a vocabulary.
 *
 * If not used in a form, don't forget to use batch_process().
 *
 * @param $options
 *   An associative array of options:
 *   - export_format : see _taxonomy_csv_values('export_format')
 *   - vocabulary_id : vocabulary id to export (default: 0, which means all)
 *   - delimiter     : one character csv delimiter (default: ',')
 *   - enclosure     : zero or one character csv enclosure (default: '"')
 *   - line_ending   : 'Unix' (default), 'Mac' or 'MS-DOS'
 *   - order         : order of terms: 'name' (default), 'tid' or 'weight'
 *   // Specific to def_links:
 *   - def_links_terms_ids : 'name_if_needed' (default), 'name' or 'tid'
 *   - def_links_vocabularies_ids : 'none' (default), 'name' or 'vid'
 *   // Level of result process infos.
 *   - check_options : boolean. check or not (default) this array of options
 *   - result_display: boolean. display or not (default). Only used with UI.
 *   - result_duplicates: boolean. display or not (default) duplicate terms.
 *   Only export_format is needed. Other options have default values.
 *   Warning: default values are different with UI.
 *
 * @return
 *   Array of errors or file object to download (need to execute batch process;
 *   result is logged in watchdog).
 */
function taxonomy_csv_export($options) {

  // Complete $options with default values if needed.
  // Default api and UI options are different.
  $options += _taxonomy_csv_values('export_default_api');

  // Presave a file in order to check access to temporary folder.
  $messages = _taxonomy_csv_export_output_presave($options);
  if (count($messages)) {
    return $messages;
  }

  // Process export.
  taxonomy_csv_vocabulary_export($options);

  // File object.
  return $options['file'];
}

/**
 * Presave output.
 *
 * Check if there is write access and prepare file.
 *
 * @param $options
 *   Array. Same as taxonomy_csv_export.
 *
 * @return
 *   Array of messages errors if any.
 *   By reference options are cleaned and completed.
 */
function _taxonomy_csv_export_output_presave(&$options) {
  $messages = array();

  // Check if there is write access and prepare file.
  // Set filename.
  if ($options['vocabulary_id']) {
    $vocabulary = taxonomy_vocabulary_load($options['vocabulary_id']);
    $vocabulary_name = $vocabulary->name;
  }
  else {
    $vocabulary_name = t('Taxonomy');
  }

  // Create file.
  $filename = file_save_data('', "{$vocabulary_name}.csv", 'FILE_EXISTS_RENAME');
  if (!$filename) {
    $messages['file'] = t('Check access rights to temp directory : export needs permission to write and to read in it. Export failed.');
  }
  else {
    $options['file'] = (object) array(
      'filename' => basename($filename),
      'filepath' => realpath($filename),
      'filesize' => filesize($filename),
    );
  }
  return $messages;
}

/**
 * Prepare the export of a vocabulary.
 * 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",
    'MSDOS' => "\r\n",
  );
  $options['end_of_line'] = $line_ending[$options['line_ending']];

  // Calculates number of terms to be exported.
  $options['total_terms'] = taxonomy_csv_term_count_in_vocabulary($options['vocabulary_id']);

  // 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...'),
    '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_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+');
    $context['sandbox']['max'] = $options['total_terms'];

    // Prepare terms to be exported.
    $context['sandbox']['terms'] = taxonomy_csv_term_load_multiple(array(), array(
      'vid' => $options['vocabulary_id'],
      'order' => $options['order'],
    ));

    // Prepare list of duplicate terms if needed.
    $context['results'][0]['duplicate_terms'] = array();
    if (in_array($options['export_format'], array(
      TAXONOMY_CSV_FORMAT_DEFINITION_LINKS,
    ))) {
      $context['results'][0]['duplicate_terms'] = taxonomy_csv_term_find_duplicate($options['vocabulary_id']);
    }
  }
  elseif (!is_resource($context['results'][0]['handle'])) {

    // Reopen file in case of memory out.
    $context['results'][0]['handle'] = fopen($options['file']->filepath, 'a+');
  }

  // Load and process one term.
  $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'];
  $term = array_shift($context['sandbox']['terms']);
  if ($term) {
    $term_number++;

    // Remember current name in case of error.
    $current_name = $term->name;

    // Process export of current term.
    $result = taxonomy_csv_term_export($term, $options, $duplicate_terms);
    $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_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'][$line_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 to display "100%" before end.
      $context['finished'] = floor($term_number / $context['sandbox']['max'] * 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) {
    $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'],
    )) . '<br />' . 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 <a href="!link">Taxonomy CSV import/export module</a>.', 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:
 *   Array of messages.
 */
function taxonomy_csv_line_export($line, $options, &$handle, $result = array()) {

  // 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.
  }
  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 : see _taxonomy_csv_values('export_format')
 *   // Specific to def_links:
 *   - def_links_terms_ids : 'name_if_needed' (default), 'name' or 'tid'
 *   - def_links_vocabularies_ids : 'none' (default), 'name' or 'vid'
 * @param $duplicate_terms
 *   (Optional) Array of duplicate terms names indexed by tid.
 *   Duplicate terms are managed only with def_links.
 *
 * @return Result array:
 *   'line' => array of exported items,
 *   'msg'  => array of messages arrays.
 */
function taxonomy_csv_term_export($term, $options, $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_ALONE_TERMS:
        $result['line'] = array(
          $term->name,
        );
        break;
      case TAXONOMY_CSV_FORMAT_DEFINITION_LINKS:

        // Prepare identifiants of main term and links.
        // Check if each term is a duplicate, because identifiants are names,
        // except for duplicate terms. For them, tid is appended to name.
        $terms = array(
          'main' => array(
            $term,
          ),
          // Synonyms are included in term object in Drupal 6.
          'parents' => taxonomy_get_parents($term->tid),
          'children' => taxonomy_get_children($term->tid),
          'relations' => taxonomy_get_related($term->tid),
        );
        foreach (array(
          'main',
          'parents',
          'children',
          'relations',
        ) as $link) {
          $ids[$link] = array();
          foreach ($terms[$link] as $item) {

            // Option is to use term id.
            if ($options['def_links_terms_ids'] == 'tid') {
              $ids[$link][] = $item->tid;
            }
            elseif (isset($duplicate_terms[$item->tid])) {
              $ids[$link][] = $item->name . ' ' . $item->tid;
            }
            else {
              $ids[$link][] = $item->name;
            }
          }
        }

        // Prepare main term vocabulary identifiant.
        // Vocabulary identifiant depends on def_links_vocabularies_ids.
        $vocabulary = taxonomy_vocabulary_load($term->vid);
        $vid = $options['def_links_vocabularies_ids'] == 'none' ? '' : $vocabulary->{$options}['def_links_vocabularies_ids'];

        // Prepare vocabularies identifiants of related terms.
        $relations_vocs = array();

        // Option 'none' is impossible with multiple vocabularies, so use name.
        $vocabulary_option = $options['def_links_vocabularies_ids'] == 'none' ? 'name' : $options['def_links_vocabularies_ids'];
        foreach ($terms['relations'] as $item) {

          // Check if related term vocabulary is main term vocabulary.
          $vocabulary = taxonomy_vocabulary_load($item->vid);
          $relations_vocs[] = $item->vid == $term->vid ? $vid : $vocabulary->{$vocabulary_option};
        }
        $result['line'] = array_merge(array(
          $term->name,
          // Main term identifiant: use tid, name or nothing.
          $options['def_links_terms_ids'] == 'name_if_needed' && $term->name == $ids['main'][0] ? '' : $ids['main'][0],
          $vid,
          $term->description,
          $term->weight,
          count($term->synonyms),
          count($ids['parents']),
          count($ids['children']),
          count($ids['relations']),
        ), $term->synonyms, $ids['parents'], $ids['children'], $ids['relations'], $relations_vocs);
        break;
      case TAXONOMY_CSV_FORMAT_TREE_STRUCTURE:
        break;
      case TAXONOMY_CSV_FORMAT_POLYHIERARCHY:
        break;
      case TAXONOMY_CSV_FORMAT_PARENTS:
        $result['line'] = array_merge(array(
          $term->name,
        ), taxonomy_csv_term_get_parents_names($term->tid));
        break;
      case TAXONOMY_CSV_FORMAT_CHILDREN:
        $result['line'] = array_merge(array(
          $term->name,
        ), taxonomy_csv_term_get_children_names($term->tid));
        break;
      case TAXONOMY_CSV_FORMAT_RELATIONS:
        $result['line'] = array_merge(array(
          $term->name,
        ), taxonomy_csv_term_get_relations_names($term->tid));
        break;
      case TAXONOMY_CSV_FORMAT_SYNONYMS:
        $result['line'] = array_merge(array(
          $term->name,
        ), $term->synonyms);
        break;
      case TAXONOMY_CSV_FORMAT_DEFINITIONS:
        $result['line'] = array_merge(array(
          $term->name,
          $term->weight,
          $term->description,
        ), $term->synonyms);
        break;
      case TAXONOMY_CSV_FORMAT_DESCRIPTIONS:
        $result['line'] = array(
          $term->name,
          $term->description,
        );
        break;
      case TAXONOMY_CSV_FORMAT_WEIGHTS:
        $result['line'] = array(
          $term->name,
          $term->weight,
        );
        break;
      case TAXONOMY_CSV_FORMAT_FIELDS:

        // @todo Extra fields.
        break;
      default:

        // Check external formats. Use it only if it works.
        $funcname = "taxonomy_csv_term_export_{$options['export_format']}";
        if (taxonomy_csv_format_check($options['export_format'], $funcname)) {
          $result = $funcname($term, $options, $duplicate_terms);
        }
        else {
          $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;
}

Functions

Namesort descending Description
taxonomy_csv_export Process the export of a vocabulary.
taxonomy_csv_line_export Export a line.
taxonomy_csv_term_export Export a term to a line matching the options.
taxonomy_csv_vocabulary_export Prepare the export of a vocabulary. If not used in a form, don't forget to use batch_process().
_taxonomy_csv_export_output_presave Presave output.
_taxonomy_csv_vocabulary_export_finished Callback for finished batch export and display result informations.
_taxonomy_csv_vocabulary_export_process Batch process of vocabulary export.