<?php
// $Id: importexportui.module,v 1.8 2006/11/08 14:01:17 jaza Exp $

/**
 * @file
 * Provides a user interface for importing and exporting data in a Drupal site.
 */

/**
 * Implementation of hook_help().
 */
function importexportui_help($section) {
  $op = '';

  if (strpos($section, 'admin/import') !== FALSE) {
    $section = 'admin/import';
    $op = 'import';
  }
  elseif (strpos($section, 'admin/export') !== FALSE) {
    $section = 'admin/export';
    $op = 'export';
  }

  switch ($section) {
    case 'admin/modules#description':
      return t('Provides a user interface for importing and exporting data in a Drupal site. Requires the importexportapi module.');
    case 'admin/help#importexportui':
      return t('The import / export UI module provides a user interface for importing and exporting data in a Drupal site in a variety of formats. It requires the importexportapi module, which it relies on for performing all of the actual work of executing import and export operations.');

    case 'admin/import':
    case 'admin/export':
      $page = 'initial selection';
      $entities = arg(2);

      if (!empty($entities)) {
        $page = 'field mappings';
      }

      return _importexportui_get_help_message($page, $op);
  }
}

/**
 * Implementation of hook_menu().
 */
function importexportui_menu($may_cache) {
  $items = array();

  if ($may_cache) {
    $items[] = array(
      'path' => 'admin/export',
      'title' => t('export'),
      'callback' => 'importexportui_page',
      'callback arguments' => array('export'),
      'access' => user_access('perform data exports')
    );
    $items[] = array(
      'path' => 'admin/import',
      'title' => t('import'),
      'callback' => 'importexportui_page',
      'callback arguments' => array('import'),
      'access' => user_access('perform data imports')
    );
  }

  return $items;
}

/**
 * Implementation of hook_perm().
 */
function importexportui_perm() {
  return array('perform data imports', 'perform data exports');
}

/**
 * Menu callback; display the import or export page.
 */
function importexportui_page($op, $entities = NULL, $engine = NULL) {
  if (!isset($entities) || !isset($engine)) {
    return importexportui_initial_selection_form($op);
  }

  $entity_list = explode(',', $entities);
  return importexportui_mapping_form($op, $entity_list, $engine);
}

/**
 * The initial selection form that the user is presented with on the import and
 * export pages.
 */
function importexportui_initial_selection_form($op) {
  $form = array();

  $form['entities'] = array(
    '#type' => 'checkboxes',
    '#title' => t('Entities to %op', array('%op' => $op)),
    '#description' => t("Select the entities that you wish to %op. The list of entities is sorted in order of dependency. For example, if entity 'z' depends on entities 'x' and 'y', then entity 'z' is listed below entities 'x' and 'y'. It is recommended that you try to %op non-dependent entities before dependent entities.", array('%op' => $op)),
    '#options' => _importexportui_get_entity_list(),
    '#required' => TRUE
  );
  $form['engine'] = array(
    '#type' => 'radios',
    '#title' => t('Format of %op data', array('%op' => $op)),
    '#description' => t("Select the format in which you wish to %op your data. The list of available formats is determined by the 'get' and 'put' engines that you have installed.", array('%op' => $op)),
    '#options' => _importexportui_get_format_list($op),
    '#required' => TRUE
  );

  $form['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Next (field mappings)')
  );

  return drupal_get_form('importexporui_initial_selection_form', $form);
}

/**
 * Redirects the user from the initial selection form to the mapping form page.
 */
function importexporui_initial_selection_form_submit($form_id, $form_values) {
  $entities = NULL;
  $engine = NULL;
  $op = arg(1);

  if (!empty($form_values['entities'])) {
    $entities = implode(',', array_filter($form_values['entities']));
  }
  if (!empty($form_values['engine'])) {
    $engine = $form_values['engine'];
  }

  if (!empty($entities) && !empty($engine)) {
    return 'admin/'. $op .'/'. $entities .'/'. $engine;
  }
}

/**
 * Displays the field mappings form.
 */
function importexportui_mapping_form($op, $entity_list, $engine) {
  $form = array();
  $def = importexportapi_get_def(NULL, TRUE);
  $type = ($op == 'export' ? 'put' : 'get');
  $file_list = ($op == 'import' ? _importexportui_list_raw_files($engine) : NULL);
  $raw_list = array();

  if ($op == 'import' && $engine == 'xml') {
    if ($engine == 'xml') {
      $form['raw'] = array(
        '#type' => 'radios',
        '#title' => t('Raw %op file', array('%op' => $op)),
        '#description' => t("Select the file containing the raw data that you wish to %op. In order to be listed here, the file must be in the '/testdata' directory of the importexportapi package, and its format must match the format that you have chosen.", array('%op' => $op)),
        '#options' => $file_list,
        '#default_value' => key($file_list),
        '#required' => TRUE
      );
    }
  }

  foreach ($entity_list as $entity_id) {
    if (isset($def[$entity_id])) {
      if ($op == 'import' && $engine == 'csv') {
        $entity_name = "'". $def[$entity_id]['#title'] ."'";
        $form['raw_'. $entity_id] = array(
          '#type' => 'radios',
          '#title' => t('Raw %op file for %entity', array('%op' => $op, '%entity' => $entity_name)),
          '#description' => t("Select the file containing the raw data for %entity that you wish to %op. In order to be listed here, the file must be in the '/testdata' directory of the importexportapi package, and its format must match the format that you have chosen.", array('%op' => $op, '%entity' => $entity_name)),
          '#options' => $file_list,
          '#default_value' => key($file_list),
          '#required' => TRUE
        );
        $raw_list_temp['csv_plural'] = $def[$entity_id]['#get']['#csv_plural'];
        $raw_list_temp['id'] = $entity_id;
        $raw_list[] = $raw_list_temp;
      }

      if (empty($def[$entity_id]["#$type"]["#$engine".'_hidden'])) {
        if (isset($def[$entity_id]["#$type"]["#$engine".'_plural'])) {
          $form[$entity_id .'_plural'] = array(
            '#type' => 'textfield',
            '#title' => $def[$entity_id]['#title'] .t(' (plural)'),
            '#default_value' => $def[$entity_id]["#$type"]["#$engine".'_plural'],
            '#required' => TRUE
          );
        }
        if (isset($def[$entity_id]["#$type"]["#$engine".'_mapping'])) {
          $form[$entity_id .'_mapping'] = array(
            '#type' => 'textfield',
            '#title' => $def[$entity_id]['#title'] .t(' (mapping)'),
            '#default_value' => $def[$entity_id]["#$type"]["#$engine".'_mapping'],
            '#required' => TRUE
          );
        }
      }

      _importexportui_mapping_form($form, $def[$entity_id], $op, $engine, $type, $file_list, $raw_list);
    }
  }

  $form['ie_op'] = array(
    '#type' => 'value',
    '#value' => $op
  );
  $form['ie_type'] = array(
    '#type' => 'value',
    '#value' => $type
  );
  $form['entity_list'] = array(
    '#type' => 'value',
    '#value' => $entity_list
  );
  if ($op == 'import' && $engine == 'csv') {
    $form['raw_list'] = array(
      '#type' => 'value',
      '#value' => $raw_list
    );
  }
  $form['engine'] = array(
    '#type' => 'value',
    '#value' => $engine
  );

  $form['back'] = array(
    '#type' => 'submit',
    '#value' => t('Back (initial selection)')
  );
  $form['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Run %op', array('%op' => $op))
  );

  return drupal_get_form('importexportui_mapping_form', $form);
}

/**
 * Recursively generates form elements for custom-defining the mappings of
 * fields at any depth.
 */
function _importexportui_mapping_form(&$form, $entity, $op, $engine, $type, $file_list, &$raw_list) {
  $field_ids = element_children($entity);

  if (!empty($field_ids)) {
    $form[$entity['#id'] .'_fields'] = array(
      '#type' => 'fieldset',
      '#title' => $entity['#title'] .t(' (fields)'),
      '#collapsible' => TRUE,
      '#collapsed' => FALSE,
      '#tree' => TRUE
    );

    foreach ($field_ids as $field_id) {
      if ($entity[$field_id]['#type'] == 'array' && $op == 'import' && $engine == 'csv') {
        $entity_name = "'". $entity[$field_id]['#title'] ."'";
        $form[$entity['#id'] .'_fields']['raw_'. $field_id] = array(
          '#type' => 'radios',
          '#title' => t('Raw %op file for %entity', array('%op' => $op, '%entity' => $entity_name)),
          '#description' => t("Select the file containing the raw data for %entity that you wish to %op. In order to be listed here, the file must be in the '/testdata' directory of the importexportapi package, and its format must match the format that you have chosen.", array('%op' => $op, '%entity' => $entity_name)),
          '#options' => $file_list,
          '#default_value' => key($file_list),
          '#required' => TRUE,
          '#tree' => FALSE
        );
        $raw_list_temp['csv_plural'] = $entity[$field_id]['#get']['#csv_plural'];
        $raw_list_temp['id'] = $field_id;
        $raw_list[] = $raw_list_temp;
      }

      if (empty($entity[$field_id]["#$type"]["#$engine".'_hidden'])) {
        if (isset($entity[$field_id]["#$type"]["#$engine".'_plural'])) {
          $form[$entity['#id'] .'_fields'][$field_id .'_plural'] = array(
            '#type' => 'textfield',
            '#title' => $entity[$field_id]['#title'] .t(' (plural)'),
            '#default_value' => $entity[$field_id]["#$type"]["#$engine".'_plural'],
            '#required' => TRUE
          );
        }
        if (isset($entity[$field_id]["#$type"]["#$engine".'_mapping'])) {
          $form[$entity['#id'] .'_fields'][$field_id .'_mapping'] = array(
            '#type' => 'textfield',
            '#title' => $entity[$field_id]['#title'] .t(' (mapping)'),
            '#default_value' => $entity[$field_id]["#$type"]["#$engine".'_mapping'],
            '#required' => TRUE
          );
        }
      }

      if ($entity[$field_id]['#type'] == 'array') {
        _importexportui_mapping_form($form[$entity['#id'] .'_fields'], $entity[$field_id], $op, $engine, $type, $file_list, $raw_list);
      }
    }
  }
}

/**
 * Either redirects the user back to the initial selection page (if they pushed
 * 'back'), or processes the user's custom-defined field mappings, and then
 * executes the import or export operation.
 */
function importexportui_mapping_form_submit($form_id, $form_values) {
  $op = isset($_POST['op']) ? $_POST['op'] : NULL;

  if (!empty($op) && $op == t('Back (initial selection)')) {
    return 'admin/'. $form_values['ie_op'];
  }

  _importexportui_set_mappings($form_values);

  $function = 'importexportui_do_'. $form_values['ie_op'];

  if (isset($op) && function_exists($function)) {
    $function($form_values);
  }

  drupal_page_footer();
  exit();
}

/**
 * Performs an export operation, and outputs the results in a themed page.
 */
function importexportui_do_export($form_values) {
  $entity_list = $form_values['entity_list'];
  $engine = $form_values['engine'];

  timer_start('importexportui');
  $data = importexportapi_get_data($entity_list);
  $ret = importexportapi_put_data($data, $engine);
  $time = timer_stop('importexportui');
  $time = $time['time'] / 1000;

  $output = '';
  $form = array();

  if (is_array($ret)) {
    $return = '';
    $count = 0;
    foreach ($ret as $title => $value) {
      $count++;
      $form['export_'. $count] = array(
        '#type' => 'textarea',
        '#title' => $title,
        '#default_value' => $value,
        '#rows' => 15,
      );
    }

    $output = $return;
  }
  else {
    $form['export'] = array(
      '#type' => 'textarea',
      '#default_value' => trim($ret),
      '#rows' => 15,
    );
  }

  $entity_count = count($data) - 1;

  $substr_pre = 'Exported ';
  $substr_post = strtr(' in %secs seconds.', array('%secs' => number_format($time, 2)));
  $form['info'] = array(
    '#value' => '<p>'. format_plural($entity_count, $substr_pre .'%count entity'. $substr_post, $substr_pre .'%count entities'. $substr_post) .'</p>'
  );

  $output .= drupal_get_form('export_form', $form);
  $output .= _importexportui_view_links($form_values['ie_op']);
  print theme('page', $output);
}

/**
 * Performs an import operation, and outputs the results in a themed page.
 */
function importexportui_do_import($form_values) {
  $entity_list = $form_values['entity_list'];
  $engine = $form_values['engine'];
  $raw = NULL;

  if ($engine == 'xml') {
    $raw = _importexportui_get_raw_data($form_values['raw']);
  }
  elseif ($engine == 'csv') {
    $raw = array();
    $raw_list = $form_values['raw_list'];

    foreach ($raw_list as $raw_item) {
      if (!empty($form_values['raw_'. $raw_item['id']])) {
        $raw[$raw_item['csv_plural']] = _importexportui_get_raw_data($form_values['raw_'. $raw_item['id']]);
      }
    }
  }

  $data = importexportapi_get_data($entity_list, $engine, array('raw' => $raw));
  $ret = importexportapi_put_data($data, NULL, array('put_id' => 1));

  $output = '<pre><code>'. check_plain("$ret") .'</code></pre>';;

  $output .= _importexportui_view_links($form_values['ie_op']);
  print theme('page', $output);
}

/**
 * Generates some useful links at the bottom of import and export result pages.
 */
function _importexportui_view_links($op) {
  $links[] = l(t('Back to mappings'), $_GET['q']);
  $links[] = l(t('New %op', array('%op' => $op)), 'admin/'. $op);

  return theme('links', $links);
}

/**
 * Returns a list of files in the '/testdata' directory that match the
 * specified format.
 */
function _importexportui_list_raw_files($format) {
  $path = drupal_get_path('module', 'importexportapi') . '/testdata';
  $files = system_listing('.*\.'. $format .'$', $path, 'name', 0);
  $file_list = array();

  foreach($files as $file) {
    $file_list[$file->basename] = $file->basename;
  }

  return $file_list;
}

/**
 * Temporary function to grab raw import data for testing.
 */
function _importexportui_get_raw_data($filename) {
  $filepath = './'. drupal_get_path('module', 'importexportapi') .'/testdata/'. $filename;
  return file_get_contents($filepath);
}

/**
 * Returns an array of all available entities, where the key of each element is
 * the internal name of the entity, and the value of each element is the title
 * of the entity.
 */
function _importexportui_get_entity_list() {
  $def = importexportapi_get_def();
  $entities = array();

  foreach ($def as $entity_id => $entity_data) {
    if (empty($entity_data['#abstract'])) {
      $entities[$entity_id] = $entity_data['#title'];
    }
  }

  return $entities;
}

/**
 * Returns an array of engines, where the key of each element is the engine's
 * and the value of each element is the engine's title.
 */
function _importexportui_get_format_list($op) {
  $type = ($op == 'export' ? 'put' : 'get');
  $formats = array();

  $engines = importexportapi_get_engines($type);

  foreach ($engines as $engine => $engine_info) {
    $formats[$engine] = $engine_info['title'];
  }
  unset($formats['db']);

  if (!empty($formats)) {
    asort($formats);
  }

  return $formats;
}

/**
 * Processes the custom-defined field mappings, as entered by the user.
 */
function _importexportui_set_mappings($form_values) {
  $def = importexportapi_get_def();

  $entity_list = $form_values['entity_list'];
  $type = $form_values['ie_type'];
  $engine = $form_values['engine'];

  foreach ($entity_list as $entity_id) {
    $values = array();

    if (!empty($form_values[$entity_id .'_plural'])) {
      $values["#$type"]['#'. $engine .'_plural'] = $form_values[$entity_id .'_plural'];
    }
    if (!empty($form_values[$entity_id .'_mapping'])) {
      $values["#$type"]['#'. $engine .'_mapping'] = $form_values[$entity_id .'_mapping'];
    }

    if (!empty($values)) {
      importexportapi_set_def($entity_id, $values);
    }

    $field_ids = element_children($def[$entity_id]);
    $child_fields = isset($form_values[$entity_id .'_fields']) ? $form_values[$entity_id .'_fields'] : NULL;

    if (!empty($field_ids) && !empty($child_fields)) {
      _importexportui_set_mappings_recurse($def[$entity_id], $field_ids, $child_fields, $type, $engine);
    }
  }
}

/**
 * Recursively processes custom-defined field mappings.
 */
function _importexportui_set_mappings_recurse($entity, $field_ids, $form_values, $type, $engine) {
  foreach ($field_ids as $field_id) {
    $values = array();

    if (!empty($form_values[$field_id .'_plural'])) {
      $values["#$type"]['#'. $engine .'_plural'] = $form_values[$field_id .'_plural'];
    }
    if (!empty($form_values[$field_id .'_mapping'])) {
      $values["#$type"]['#'. $engine .'_mapping'] = $form_values[$field_id .'_mapping'];
    }

    if (!empty($values)) {
      $field_path = array_merge($entity[$field_id]['#parents'], array($field_id));
      importexportapi_set_def($field_path, $values);
    }

    $child_ids = element_children($entity[$field_id]);
    $child_fields = isset($form_values[$field_id .'_fields']) ? $form_values[$field_id .'_fields'] : NULL;

    if (!empty($child_ids) && !empty($child_fields)) {
      _importexportui_set_mappings_recurse($entity[$field_id], $child_ids, $child_fields, $type, $engine);
    }
  }
}

/**
 * Defines the help messages that are available on the import and export pages.
 */
function _importexportui_get_help_message($page, $op) {
  switch ($page) {
    case 'initial selection':
      return '<p>'. t("<strong>Step 1: initial selection</strong>. To begin your data %op, select the entities that you wish to %op, and the format in which you wish to %op them.", array('%op' => $op)) .'</p>';

    case 'field mappings':
      $ie_op = isset($_POST['op']) ? $_POST['op'] : NULL;
      $output = '';

      if ($ie_op != t('Run %op', array('%op' => $op))) {
        $output = '<p>'. t("<strong>Step 2: field mappings</strong>. You can define custom mappings for all the fields of your %op data, or you can leave all fields at their defaults. Singular and/or plural mappings may be available, depending on the nature of each field, and the way in which each field is rendered by your chosen format.", array('%op' => $op));

        if ($op == 'import') {
          $output .= t(" Additionally, you must select the source of your raw %op data at this stage.", array('%op' => $op));
        }
        $output .= '</p>';
      }
      else {
        $output = '<p>'. t("Your %op has been completed. The results of the %op can be seen below.", array('%op' => $op)) .'</p>';
      }

      return $output;

    default;
      return '';
  }
}
