$file) {
if (module_exists($name)) {
require_once('./'. $file->filename);
}
}
// Make sure the import directory is created.
node_import_directory();
/**
* Returns the API version of node_import.
*
* Currently versions are/were:
* - 1.x : 4.7.x version (unsupported),
* - 2.x : 5.x version (unsupported),
* - 3.x : 6.x-1.x version (current).
*
* @return
* A string similar to PHP or Drupal version. Major versions
* will not change function prototypes or return values. Minor
* versions may add additional keys to returned arrays and
* add non-required parameters to functions.
*
* Note that the version does not reflect the version of the
* support files only of the API.
*/
function node_import_version() {
return '3.0';
}
/**
* @defgroup node_import_hooks Node import hooks
* @{
*/
/**
* Returns a list of available content types.
*
* @param $check_access
* Boolean. If TRUE, only the types the user can create are
* returned. If FALSE, all types are returned.
*
* @param $reset
* Boolean. If TRUE, the internal cache is rebuilt.
*
* @return
* Array of types. See hook_node_import_types().
*/
function node_import_types($check_access = TRUE, $reset = FALSE) {
static $types;
static $allowed_types;
if (!isset($types) || $reset) {
$defaults = array(
'title' => '',
'can_create' => FALSE,
'create' => '',
);
$utypes = (array)module_invoke_all('node_import_types');
foreach ($utypes as $type => $typeinfo) {
$utypes[$type] = array_merge($defaults, $typeinfo);
}
drupal_alter('node_import_types', $utypes);
$types = array_map('strip_tags', node_import_extract_property($utypes, 'title'));
asort($types);
foreach ($types as $type => $title) {
$types[$type] = $utypes[$type];
}
$allowed_types = array();
foreach ($types as $type => $typeinfo) {
if ($typeinfo['can_create'] === TRUE || (function_exists($function = $typeinfo['can_create']) && $function($type) == TRUE)) {
$allowed_types[$type] = $typeinfo;
}
}
}
return $check_access ? $allowed_types : $types;
}
/**
* Returns a list of available content fields for given
* node_import type.
*
* @param $type
* String. The node_import type.
*
* @param $reset
* Boolean. If TRUE, the internal cache is rebuilt.
*
* @return
* Array of fields. See hook_node_import_fields().
*/
function node_import_fields($type, $reset = FALSE) {
static $fields;
if (!isset($fields[$type]) || $reset) {
$defaults = array(
'title' => '',
'tips' => array(),
'group' => '',
'module' => '',
'weight' => 0,
'is_mappable' => TRUE,
'map_required' => FALSE,
'has_multiple' => FALSE,
'multiple_separator' => variable_get('node_import:multiple_separator', '||'),
'has_hierarchy' => FALSE,
'hierarchy_separator' => variable_get('node_import:hierarchy_separator', '>>'),
'hierarchy_reverse' => FALSE,
'input_format' => '',
'preprocess' => array(),
'allowed_values' => array(),
'default_value' => NULL,
'allow_empty' => FALSE,
'map_required' => FALSE,
'is_checkboxes' => FALSE,
);
$fields[$type] = (array)module_invoke_all('node_import_fields', $type);
foreach ($fields[$type] as $fieldname => $fieldinfo) {
// Merge sane defaults.
$fields[$type][$fieldname] = $fieldinfo = array_merge($defaults, $fieldinfo);
// Add preprocessors for builtin input_formats.
if (!empty($fieldinfo['allowed_values'])) {
$fields[$type][$fieldname]['preprocess'][] = 'node_import_check_values';
$s = '';
foreach ($fieldinfo['allowed_values'] as $key => $value) {
if (drupal_strlen($s) > 60) {
$s .= ', …';
break;
}
$s .= ($s == '' ? '' : ', ') . check_plain(drupal_strtolower($key)) . ': ' . check_plain(drupal_strtolower($value));
}
$fields[$type][$fieldname]['tips'][] = t('Allowed values (!values).', array('!values' => $s));
}
switch ($fieldinfo['input_format']) {
case 'boolean':
$fields[$type][$fieldname]['preprocess'][] = 'node_import_check_boolean';
$fields[$type][$fieldname]['tips'][] = t('Boolean (0/1, off/on, no/yes, false/true).');
break;
case 'date':
$fields[$type][$fieldname]['preprocess'][] = 'node_import_check_date';
$fields[$type][$fieldname]['tips'][] = t('Date ("YYYY-MM-DD HH:MM" or specified custom format).');
break;
case 'email':
$fields[$type][$fieldname]['preprocess'][] = 'node_import_check_email';
break;
case 'filepath':
$fields[$type][$fieldname]['preprocess'][] = 'node_import_check_filepath';
break;
case 'node_reference':
$fields[$type][$fieldname]['preprocess'][] = 'node_import_check_node_reference';
$fields[$type][$fieldname]['tips'][] = t('Node reference (by nid or title).');
break;
case 'user_reference':
$fields[$type][$fieldname]['preprocess'][] = 'node_import_check_user_reference';
$fields[$type][$fieldname]['tips'][] = t('User reference (by uid, name or mail).');
break;
case 'weight':
$fields[$type][$fieldname]['preprocess'][] = 'node_import_check_weight';
break;
}
}
drupal_alter('node_import_fields', $fields[$type], $type);
// Sort by weight.
uasort($fields[$type], 'node_import_sort');
}
return $fields[$type];
}
/**
* Returns a list of default (form elements).
*
* @param $type
* String. The node_import type.
*
* @param $defaults
* Array of currently filled in values.
*
* @param $fields
* Array of available fields.
*
* @param $map
* Array of how fields are mapped.
*
* @return
* FAPI array. See hook_node_import_defaults().
*/
function node_import_defaults($type, $defaults, $fields, $map) {
if (!is_array($defaults)) {
$defaults = array();
}
$form = (array)module_invoke_all('node_import_defaults', $type, $defaults, $fields, $map);
foreach ($fields as $fieldname => $fieldinfo) {
// Set #node_import-group if not set.
if (isset($form[$fieldname]) && !isset($form[$fieldname]['#node_import-group'])) {
$form[$fieldname]['#node_import-group'] = $fieldinfo['group'];
}
// Set #weight if not set.
if (isset($form[$fieldname]) && !isset($form[$fieldname]['#weight'])) {
$form[$fieldname]['#weight'] = $fieldinfo['weight'];
}
// Set #description if not set.
if (isset($form[$fieldname]) && !isset($form[$fieldname]['#description'])) {
if (count($fieldinfo['tips']) > 1) {
$form[$fieldname]['#description'] = '
';
$form[$fieldname]['#description'] .= '- ' . implode('
- ', $fieldinfo['tips']) . '
';
$form[$fieldname]['#description'] .= '
';
}
else if (!empty($fieldinfo['tips'])) {
$form[$fieldname]['#description'] = implode('', $fieldinfo['tips']);
}
}
// Set default_value as value.
if (!empty($fieldinfo['default_value']) && !isset($form[$fieldname])) {
$form[$fieldname] = array(
'#type' => 'value',
'#value' => $fieldinfo['default_value'],
);
}
}
drupal_alter('node_import_defaults', $form, $type, $defaults, $fields, $map);
return $form;
}
/**
* Returns a list of options (form elements).
*
* @param $type
* String. The node_import type.
*
* @param $options
* Array of currently filled in values.
*
* @param $fields
* Array of available fields.
*
* @param $map
* Array of how fields are mapped.
*
* @return
* FAPI array. See hook_node_import_options().
*/
function node_import_options($type, $options, $fields, $map) {
if (!is_array($options)) {
$options = array();
}
$form = (array)module_invoke_all('node_import_options', $type, $options, $fields, $map);
// Copy from modules/system/system.admin.inc
$date_short = array('Y-m-d H:i', 'm/d/Y - H:i', 'd/m/Y - H:i', 'Y/m/d - H:i',
'd.m.Y - H:i', 'm/d/Y - g:ia', 'd/m/Y - g:ia', 'Y/m/d - g:ia',
'M j Y - H:i', 'j M Y - H:i', 'Y M j - H:i',
'M j Y - g:ia', 'j M Y - g:ia', 'Y M j - g:ia');
$date_short_choices = array();
foreach ($date_short as $f) {
$date_short_choices[$f] = format_date(time(), 'custom', $f);
}
$date_short_choices['custom'] = t('Custom format');
$timezones = date_timezone_names(TRUE);
foreach ((array)$fields as $fieldname => $fieldinfo) {
// Set #node_import-group if not set.
if (isset($form[$fieldname]) && !isset($form[$fieldname]['#node_import-group'])) {
$form[$fieldname]['#node_import-group'] = $fieldinfo['group'];
}
// Set #description if not set.
if (isset($form[$fieldname]) && !isset($form[$fieldname]['#description'])) {
if (count($fieldinfo['tips']) > 1) {
$form[$fieldname]['#description'] = '';
$form[$fieldname]['#description'] .= '- ' . implode('
- ', $fieldinfo['tips']) . '
';
$form[$fieldname]['#description'] .= '
';
}
else if (!empty($fieldinfo['tips'])) {
$form[$fieldname]['#description'] = implode('', $fieldinfo['tips']);
}
}
// Set #weight if not set.
if (isset($form[$fieldname]) && !isset($form[$fieldname]['#weight'])) {
$form[$fieldname]['#weight'] = $fieldinfo['weight'];
}
$map_count = node_import_field_map_count($fieldname, $map);
// Add multiple_separator option for fields that can have multiple
// values AND that is mapped to exactly one file column.
if ($fieldinfo['has_multiple'] &&
$map_count == 1 &&
(!isset($form[$fieldname]) || !isset($form[$fieldname]['multiple_separator']))) {
if (!isset($form[$fieldname])) {
$form[$fieldname] = array(
'#title' => $fieldinfo['title'],
'#node_import-group' => $fieldinfo['group'],
);
}
$form[$fieldname]['multiple_separator'] = array(
'#type' => 'textfield',
'#title' => t('Multiple values are separated by'),
'#size' => 6,
'#default_value' => isset($options[$fieldname]['multiple_separator']) ? $options[$fieldname]['multiple_separator'] : $fieldinfo['multiple_separator'],
);
}
// Add hierarchy_separator option for fields that can have
// hierarchical values AND (that can have multiple values OR
// that can not have multiple values but is mapped to exactly
// one file column).
if ($fieldinfo['has_hierarchy'] &&
($fieldinfo['has_multiple'] || $map_count == 1) &&
$map_count > 0 &&
(!isset($form[$fieldname]) || !isset($form[$fieldname]['hierarchy_separator']))) {
if (!isset($form[$fieldname])) {
$form[$fieldname] = array(
'#title' => $fieldinfo['title'],
'#node_import-group' => $fieldinfo['group'],
);
}
$form[$fieldname]['hierarchy_separator'] = array(
'#type' => 'textfield',
'#title' => t('Hierarchy is specified by'),
'#size' => 6,
'#default_value' => isset($options[$fieldname]['hierarchy_separator']) ? $options[$fieldname]['hierarchy_separator'] : $fieldinfo['hierarchy_separator'],
);
}
// Add hierarchy_reverse option for fields that can have
// hierarchical values AND that can not have multiple values
// AND that is mapped to more than one file column.
if ($fieldinfo['has_hierarchy'] &&
$map_count > 1 && !$fieldinfo['has_multiple'] &&
(!isset($form[$fieldname]) || !isset($form[$fieldname]['hierarchy_reverse']))) {
if (!isset($form[$fieldname])) {
$form[$fieldname] = array(
'#title' => $fieldinfo['title'],
'#node_import-group' => $fieldinfo['group'],
);
}
if ($map_count > 1 && !$fieldinfo['has_multiple']) {
$form[$fieldname]['hierarchy_reverse'] = array(
'#type' => 'checkbox',
'#title' => t('Reverse file columns for hierarchy'),
'#default_value' => isset($options[$fieldname]['hierarchy_reverse']) ? $options[$fieldname]['hierarchy_reverse'] : $fieldinfo['hierarchy_reverse'],
);
}
}
// Add a custom date format option for fields that are dates.
if ($fieldinfo['input_format'] == 'date' && $map_count > 0) {
if (!isset($form[$fieldname])) {
$form[$fieldname] = array(
'#title' => $fieldinfo['title'],
'#node_import-group' => $fieldinfo['group'],
);
}
$form[$fieldname]['timezone'] = array(
'#type' => 'select',
'#title' => t('Timezone'),
'#default_value' => isset($options[$fieldname]['timezone']) ? $options[$fieldname]['timezone'] : date_default_timezone_name(),
'#options' => $timezones,
'#description' => t('Select the default time zone. If in doubt, choose the timezone that is closest to your location which has the same rules for daylight saving time.'),
);
$form[$fieldname]['date_format'] = array(
'#type' => 'select',
'#title' => t('Date format'),
'#default_value' => isset($options[$fieldname]['date_format']) ? $options[$fieldname]['date_format'] : variable_get('date_format_short', 'm/d/Y - H::i'),
'#options' => $date_short_choices,
'#description' => t('Select the date format for import. If you choose Custom format enter the custom format below.'),
);
$form[$fieldname]['date_custom'] = array(
'#type' => 'textfield',
'#title' => t('Custom date format'),
'#attributes' => array('class' => 'custom-format'),
'#default_value' => isset($options[$fieldname]['date_custom']) ? $options[$fieldname]['date_custom'] : variable_get('date_format_short', 'm/d/Y - H::i'),
'#description' => t('See the PHP manual for available options.', array('@url' => 'http://php.net/manual/function.date.php')),
);
}
// Add directory selection for fields that are filepaths.
if ($fieldinfo['input_format'] == 'filepath' && $map_count > 0) {
if (!isset($form[$fieldname])) {
$form[$fieldname] = array(
'#title' => $fieldinfo['title'],
'#node_import-group' => $fieldinfo['group'],
);
}
$form[$fieldname][] = array(
'#value' => t('You need to FTP the files you reference in this field manually to the correct location (%path) before doing the import.', array('%path' => file_create_path(isset($fieldinfo['to_directory']) ? $fieldinfo['to_directory'] : ''))),
);
$form[$fieldname]['manually_moved'] = array(
'#type' => 'hidden',
'#value' => TRUE,
);
/*
TODO: we disable this until I get the time to figure out why files are not
moved to the correct location if you do not move them manually.
//TODO: as the directory must be relative... we could let the user choose from a select box
//TODO: we might use the sample data to find the correct directory
$form[$fieldname]['from_directory'] = array(
'#type' => 'textfield',
'#title' => t('Copy from'),
'#field_prefix' => node_import_directory() . '/',
'#field_suffix' => '/',
'#default_value' => isset($options[$fieldname]['from_directory']) ? $options[$fieldname]['from_directory'] : '',
'#description' => t('Fill in the directory which contains the files.'),
);
$form[$fieldname]['manually_moved'] = array(
'#type' => 'checkbox',
'#title' => t('Files have been manually moved'),
'#default_value' => isset($options[$fieldname]['manually_moved']) ? $options[$fieldname]['manually_moved'] : 0,
'#description' => t('Check this box if you have already moved the files to the correct location on your server (%path).', array('%path' => file_create_path(isset($fieldinfo['to_directory']) ? $fieldinfo['to_directory'] : ''))),
);
*/
/* TODO
$form[$fieldname]['delete_on_success'] = array(
'#type' => 'checkbox',
'#title' => t('Delete files after import'),
'#default_value' => isset($options[$fieldname]['delete_on_success']) ? $options[$fieldname]['delete_on_success'] : 1,
'#description' => t('Check this box if you want to delete the files from the server after a succesful import.'),
);
*/
}
}
drupal_alter('node_import_options', $form, $type, $options, $fields, $map);
return $form;
}
/**
* Create an array of values to submit to the form.
*
* @param $type
* String. The node_import type.
*
* @param $data
* Array of data from the file as ($col_index => $value).
*
* @param $map
* Array of how the data maps to fields.
*
* @param $defaults
* Array of default values.
*
* @param $options
* Array of options.
*
* @param $fields
* Array of available fields.
*
* @param $preview
* Boolean. If TRUE a preview will be created. If FALSE construct the
* values for real.
*
* @return
* Array of values to submit to a form. See hook_node_import_values().
*/
function node_import_values($type, $data, $map, $defaults, $options, $fields, $preview) {
$values = module_invoke_all('node_import_values', $type, $defaults, $options, $fields, $preview);
foreach ($fields as $fieldname => $fieldinfo) {
$map[$fieldname] = is_array($map[$fieldname]) ? $map[$fieldname] : array($map[$fieldname]);
$map_count = node_import_field_map_count($fieldname, $map);
$mseparator = isset($options[$fieldname]['multiple_separator']) ? $options[$fieldname]['multiple_separator'] : $fieldinfo['multiple_separator'];
$hseparator = isset($options[$fieldname]['hierarchy_separator']) ? $options[$fieldname]['hierarchy_separator'] : $fieldinfo['hierarchy_separator'];
$hreverse = isset($options[$fieldname]['hierarchy_reverse']) ? $options[$fieldname]['hierarchy_reverse'] : $fieldinfo['hierarchy_reverse'];
// Merge default value for each field.
if (isset($defaults[$fieldname])) {
if ($fieldinfo['is_checkboxes']) {
$values[$fieldname] = array_keys(array_filter($defaults[$fieldname]));
}
else {
$values[$fieldname] = $defaults[$fieldname];
}
}
else if (isset($fieldinfo['default_value'])) {
$values[$fieldname] = $fieldinfo['default_value'];
}
// Map the data ONLY IF the data to map is not empty.
if ($fieldinfo['has_multiple']) {
if ($map_count > 0) {
$fieldvalues = array();
foreach ($map[$fieldname] as $col) {
$value = isset($data[$col]) ? (string)$data[$col] : '';
if ($map_count == 1 && strlen($mseparator) > 0) {
$fieldvalues = strlen($value) > 0 ? array_map('trim', explode($mseparator, $value)) : array();
break;
}
$fieldvalues[] = $value;
}
if (!$fieldinfo['allow_empty']) {
$fieldvalues = array_filter($fieldvalues, 'drupal_strlen');
}
if ($fieldinfo['has_hierarchy'] && strlen($hseparator) > 0) {
foreach ($fieldvalues as $i => $value) {
$fieldvalues[$i] = strlen($value) > 0 ? array_map('trim', explode($hseparator, $value)) : array($value);
}
}
if (empty($fieldvalues) && isset($values[$fieldname])) {
$values[$fieldname] = $values[$fieldname];
}
else {
$values[$fieldname] = $fieldvalues;
}
}
}
else {
if ($map_count > 0 && $fieldinfo['has_hierarchy']) {
$fieldvalues = array();
foreach ($map[$fieldname] as $col) {
$value = isset($data[$col]) ? (string)$data[$col] : '';
if ($map_count == 1 && strlen($hseparator) > 0) {
$fieldvalues = drupal_strlen($value) > 0 ? array_map('trim', explode($hseparator, $value)) : array();
break;
}
$fieldvalues[] = $value;
}
if (!$fieldinfo['allow_empty']) {
$fieldvalues = array_filter($fieldvalues, 'drupal_strlen');
}
if ($hreverse) {
$fieldvalues = array_reverse($fieldvalues);
}
$values[$fieldname] = empty($fieldvalues) ? $values[$fieldname] : $fieldvalues;
}
else if ($map_count == 1) {
foreach ($map[$fieldname] as $col) {
$value = isset($data[$col]) ? (string)$data[$col] : '';
}
$values[$fieldname] = drupal_strlen($value) > 0 ? $value : (isset($values[$fieldname]) ? $values[$fieldname] : '');
}
$values[$fieldname] = array(isset($values[$fieldname]) ? $values[$fieldname] : '');
}
// Preprocess the data as long as the value is not empty and it
// validates.
foreach ((array)$values[$fieldname] as $i => $value) {
foreach ($fieldinfo['preprocess'] as $function) {
if ((is_array($values[$fieldname][$i]) && !empty($values[$fieldname][$i])) // has_multiple values are arrays
|| (is_string($values[$fieldname][$i]) && drupal_strlen($values[$fieldname][$i]) > 0)) {
$options[$fieldname] = isset($options[$fieldname]) ? $options[$fieldname] : array();
$return = $function($values[$fieldname][$i], $fieldinfo, $options[$fieldname], $preview);
if ($return === FALSE) {
$values[$fieldname][$i] = '';
continue 2;
}
else if ($return === TRUE) {
continue 2;
}
}
}
}
// If empty values are not allowed, filter them out.
if (!$fieldinfo['allow_empty']) {
$values[$fieldname] = array_filter((array)$values[$fieldname], 'drupal_strlen');
}
// Handle files specially. The preprocess function only returns
// the path - we need to make sure we save the file now into the
// db and set the value to the fid. We need to do this here instead
// of in the preprocess function because we need $values['uid'].
if ($fieldinfo['input_format'] == 'filepath') {
foreach ($values[$fieldname] as $i => $value) {
if (drupal_strlen($value) > 0) {
$result = db_result(db_query("SELECT fid FROM {files} WHERE filepath = '%s'", $value));
if ($result) {
$values[$fieldname][$i] = $result;
}
else {
// TODO: don't we need more stuff - eg run the validators?
global $user;
$file = new stdClass();
$file->uid = isset($values['uid']) ? $values['uid'] : $user->uid;
$file->filename = basename($value);
$file->filepath = $value;
$file->filesize = filesize($value);
$file->filemime = file_get_mimetype($file->filename);
$file->status = FILE_STATUS_TEMPORARY;
$file->timestamp = time();
drupal_write_record('files', $file);
$values[$fieldname][$i] = $file->fid;
}
}
}
}
// If only a single value is allowed, get the first one.
if (!$fieldinfo['has_multiple']) {
$values[$fieldname] = array_shift($values[$fieldname]);
}
// Convert checkboxes fields to a format FAPI understands.
if ($fieldinfo['is_checkboxes'] && !empty($values[$fieldname])) {
// Only in PHP > 5.2: $values[$fieldname] = array_fill_keys($values[$fieldname], 1);
$values[$fieldname] = array_combine($values[$fieldname], array_fill(0, count($values[$fieldname]), 1));
}
}
drupal_alter('node_import_values', $values, $type, $defaults, $options, $fields, $preview);
return $values;
}
/**
* Get a list of options for different stuff presented to the
* user in the wizard form such as 'record separators', ...
*
* @param $op
* String. See hook_node_import_format_options().
*
* @param $reset
* Boolean. If TRUE, the internal cache is reset.
*
* @return
* Array.
*/
function node_import_format_options($op, $reset = FALSE) {
static $options;
if (!isset($options) || ($reset && !isset($op))) {
$options = array();
}
if (isset($op) && (!isset($options[$op]) || $reset)) {
$options[$op] = module_invoke_all('node_import_format_options', $op);
drupal_alter('node_import_format_options', $options[$op], $op);
}
return isset($op) ? $options[$op] : array();
}
/**
* @}
*/
/**
* Import a number of rows from all available tasks. Should only be called
* from within hook_cron() or from a JS callback as this function may take
* a long time.
*
* The function ends when $count $units have been finished. For example
* @code
* node_import_do_all_tasks('all');
* node_import_do_all_tasks('rows', 10);
* node_import_do_all_tasks('bytes', 4096);
* node_import_do_all_tasks('ms', 1000);
* @endcode
*
* @param $unit
* String. Either 'rows', 'bytes', 'ms' (milliseconds) or 'all'.
* Defaults to 'all'.
*
* @param $count
* Integer. Number of $units to do. Defaults to 0 (in which case
* exactly one row will be imported if $unit != 'all').
*
* @return
* Nothing.
*/
function node_import_do_all_tasks($unit = 'all', $count = 0) {
global $node_import_can_continue;
$byte_count = 0;
$row_count = 0;
timer_start('node_import:do_all_tasks');
foreach (node_import_list_tasks(TRUE) as $taskid => $task) {
$bytes = $task['file_offset'];
$rows = $task['row_done'] + $task['row_error'];
node_import_do_task($task, $unit, $count);
$byte_count += $task['file_offset'] - $bytes;
$row_count += $task['row_done'] + $task['row_error'] - $rows;
if ($node_import_can_continue && ($unit == 'all'
|| ($unit == 'bytes' && $byte_count < $count)
|| ($unit == 'rows' && $row_count < $count)
|| ($unit == 'ms' && timer_read('node_import:do_all_tasks') < $count))) {
continue;
}
break;
}
timer_stop('node_import:do_all_tasks');
}
/**
* Import a number of rows from the specified task. Should only be called
* from within hook_cron() or from a JS callback as this function may take
* a long time.
*
* The function ends when $count $units have been finished. For example
* @code
* node_import_do_task($task, 'all');
* node_import_do_task($task, 'rows', 10);
* node_import_do_task($task, 'bytes', 4096);
* node_import_do_task($task, 'ms', 1000);
* @endcode
*
* @param $task
* Array. The task to continue. Note that this is passed by reference!
* So you can check the status of the task after running this function
* without having to query the database.
*
* @param $unit
* String. Either 'rows', 'bytes', 'ms' (milliseconds) or 'all'.
* Defaults to 'all'.
*
* @param $count
* Integer. Number of $units to do. Defaults to 0 (in which case
* exactly one row will be imported if $unit != 'all').
*
* @return
* The status of each imported row (error or not) is stored in the
* database. @see node_import_constants.
*/
function node_import_do_task(&$task, $unit = 'all', $count = 0) {
global $node_import_can_continue;
$node_import_can_continue = TRUE;
if ($task['status'] != NODE_IMPORT_STATUS_DONE && node_import_lock_acquire()) {
global $user;
$backup_user = $user;
if ($task['uid'] != $user->uid) {
session_save_session(FALSE);
$user = user_load(array('uid' => $task['uid']));
}
$taskid = $task['taskid'];
$file_offset = 0;
$byte_count = 0;
$row_count = 0;
timer_start('node_import:do_task:'. $taskid);
$data = array();
switch ($task['status']) {
case NODE_IMPORT_STATUS_PENDING:
if ($task['file_offset'] == 0 && $task['has_headers']) {
list($file_offset, $data) = node_import_read_from_file($task['file']->filepath, $file_offset, $task['file_options']);
}
else {
$file_offset = $task['file_offset'];
}
break;
case NODE_IMPORT_STATUS_ERROR:
$task['status'] = NODE_IMPORT_STATUS_DONE;
$file_offset = $task['file']->filesize; //TODO
break;
}
module_invoke_all('node_import_task', $task, 'continue');
while ($task['status'] != NODE_IMPORT_STATUS_DONE) {
list($new_offset, $data) = node_import_read_from_file($task['file']->filepath, $file_offset, $task['file_options']);
if (is_array($data)) {
switch ($task['status']) {
case NODE_IMPORT_STATUS_PENDING:
db_query("DELETE FROM {node_import_status} WHERE taskid = %d AND file_offset = %d", $taskid, $file_offset);
db_query("INSERT INTO {node_import_status} (taskid, file_offset, errors) VALUES (%d, %d, '%s')", $taskid, $file_offset, serialize(array()));
break;
case NODE_IMPORT_STATUS_ERROR:
db_query("UPDATE {node_import_status} SET errors = '%s', status = %d WHERE taskid = %d AND file_offset = %d", serialize(array()), NODE_IMPORT_STATUS_PENDING, $taskid, $file_offset);
break;
}
db_query("UPDATE {node_import_tasks} SET file_offset = %d, changed = %d WHERE taskid = %d", $new_offset, time(), $taskid);
$task['file_offset'] = $new_offset;
$errors = node_import_create($task['type'], $data, $task['map'], $task['defaults'], $task['options'], FALSE);
if (is_array($errors)) {
db_query("UPDATE {node_import_status} SET status = %d, errors = '%s' WHERE taskid = %d AND file_offset = %d", NODE_IMPORT_STATUS_ERROR, serialize($errors), $taskid, $file_offset);
db_query("UPDATE {node_import_tasks} SET row_error = row_error + 1 WHERE taskid = %d", $taskid);
$task['row_error']++;
}
else {
db_query("UPDATE {node_import_status} SET status = %d, objid = %d WHERE taskid = %d AND file_offset = %d", NODE_IMPORT_STATUS_DONE, $errors, $taskid, $file_offset);
db_query("UPDATE {node_import_tasks} SET row_done = row_done + 1 WHERE taskid = %d", $taskid);
$task['row_done']++;
}
$byte_count += $new_offset - $file_offset;
$row_count++;
}
else {
db_query("UPDATE {node_import_tasks} SET status = %d, file_offset = %d WHERE taskid = %d", NODE_IMPORT_STATUS_DONE, $task['file']->filesize, $taskid);
$task['status'] = NODE_IMPORT_STATUS_DONE;
$task['file_offset'] = $task['file']->filesize;
}
switch ($task['status']) {
case NODE_IMPORT_STATUS_PENDING:
$file_offset = $new_offset;
break;
case NODE_IMPORT_STATUS_ERROR:
$file_offset = $task['file']->filesize; //TODO
break;
}
if ($node_import_can_continue && ($unit == 'all'
|| ($unit == 'bytes' && $byte_count < $count)
|| ($unit == 'rows' && $row_count < $count)
|| ($unit == 'ms' && timer_read('node_import:do_task:'. $taskid) < $count))) {
continue;
}
break;
}
module_invoke_all('node_import_task', $task, 'pause');
// Cleanup before exit.
$user = $backup_user;
session_save_session(TRUE);
timer_stop('node_import:do_task:'. $taskid);
node_import_lock_release();
}
}
/**
* JS callback to continue the specified task and returns the status
* of it. This function will take at most one second.
*
* @param $task
* Full loaded $task.
*
* @return
* JSON.
*/
function node_import_js($task) {
node_import_do_task($task, 'ms', 1000);
echo drupal_json(array(
'status' => 1,
'message' => format_plural($task['row_done'], t('1 row imported'), t('@count rows imported')) .'
'.
format_plural($task['row_error'], t('1 row with errors'), t('@count rows with errors')),
'percentage' => $task['status'] == NODE_IMPORT_STATUS_DONE ? 100 : round(floor(100.0 * $task['file_offset'] / $task['file']->filesize), 0),
));
exit();
}
/**
* Create a new object of specified $type.
*
* @param $type
* String. The node_import type.
*
* @param $data
* Array of data from the file as ($col_index => $value).
*
* @param $map
* Array of how the data maps to fields.
*
* @param $defaults
* Array of default values.
*
* @param $options
* Array of options.
*
* @param $preview
* Boolean.
*
* @return
* The return value is: if $preview is TRUE, a string with the preview
* of the object is returned. If $preview is FALSE, an unique identifier
* is returned or an array with errors if failed.
*/
function node_import_create($type, $data, $map, $defaults, $options, $preview) {
$output = $preview ? '' : array();
// Reset execution time. Note that this only works when SAFE_MODE is OFF but
// it has no side-effects if SAFE_MODE is ON. See
// http://php.net/manual/en/function.set-time-limit.php
set_time_limit(variable_get('node_import:set_time_limit', 60));
$types = node_import_types();
$fields = node_import_fields($type);
// We need to clean out the form errors before submitting.
form_set_error(NULL, '', TRUE);
// Create a list of values to submit.
$values = node_import_values($type, $data, $map, $defaults, $options, $fields, $preview);
// Submit it for preview or creation.
if (function_exists($function = $types[$type]['create'])) {
$output = $function($type, $values, $preview);
}
module_invoke_all('node_import_postprocess', $type, $values, $options, $preview);
// Check for errors and clear them again.
if ($preview) {
$output = theme('status_messages') . $output;
}
if (($errors = form_get_errors())) {
if ($preview) {
$output .= 'values = '. print_r($values, TRUE) .'
'; //TODO: show data instead?
}
else {
$output = $errors;
}
}
form_set_error(NULL, '', TRUE);
drupal_get_messages(NULL, TRUE); // Otherwise they are still showed.
return $output;
}
/**
* @defgroup node_import_tasks Node import tasks
* @{
*/
/**
* Create a new import task.
*
* @param $values
* Array of filled in values.
*
* @return
* Integer. Unique identifier for the task or FALSE if the task could
* not be saved to the database.
*/
function node_import_save_task($values) {
global $user;
if (!isset($values['uid'])) {
$values['uid'] = $user->uid;
}
if (!isset($values['created'])) {
$values['created'] = time();
}
if (!isset($values['changed'])) {
$values['changed'] = time();
}
if (drupal_write_record('node_import_tasks', $values) === SAVED_NEW) {
module_invoke_all('node_import_task', $values, 'insert');
return $values['taskid'];
}
return FALSE;
}
/**
* Get a list of available tasks.
*
* @param $all
* Boolean. If TRUE, all tasks are returned. If FALSE, only the tasks
* the current user has access to.
*
* @return
* Array of tasks.
*/
function node_import_list_tasks($all = FALSE) {
global $user;
$tasks = array();
if ($all || user_access('administer imports')) {
$result = db_query("SELECT * FROM {node_import_tasks} ORDER BY created ASC");
}
else {
$result = db_query("SELECT * FROM {node_import_tasks} WHERE uid = %d ORDER BY created ASC", $user->uid);
}
while (($task = db_fetch_array($result))) {
foreach (array('file_options', 'headers', 'map', 'defaults', 'options') as $key) {
$task[$key] = isset($task[$key]) ? unserialize($task[$key]) : array();
}
$task['file'] = db_fetch_object(db_query("SELECT * FROM {files} WHERE fid = %d", $task['fid']));
$tasks[$task['taskid']] = $task;
}
return $tasks;
}
/**
* Delete an import task.
*
* @param $taskid
* Unique identifier.
*
* @return
* Nothing.
*/
function node_import_delete_task($taskid) {
db_query("DELETE FROM {node_import_tasks} WHERE taskid = %d", $taskid);
db_query("DELETE FROM {node_import_status} WHERE taskid = %d", $taskid);
module_invoke_all('node_import_task', $taskid, 'delete');
}
/**
* @}
*/
/**
* @defgroup node_import_preprocess Node import preprocess functions
* @{
*/
/**
* Check if the value is a valid boolean (1, 0, true, false, yes, no, on, off).
*
* Uses: nothing.
*/
function node_import_check_boolean(&$value, $field, $options, $preview) {
static $trues;
static $falses;
if (!isset($trues)) {
$trues = array(
'1',
'on', drupal_strtolower(t('On')),
'yes', drupal_strtolower(t('Yes')),
'true', drupal_strtolower(t('True')),
);
$falses = array(
'0',
'off', drupal_strtolower(t('Off')),
'no', drupal_strtolower(t('No')),
'false', drupal_strtolower(t('False')),
);
}
if (in_array(drupal_strtolower($value), $trues, TRUE)) {
$value = '1';
return TRUE;
}
else if (in_array(drupal_strtolower($value), $falses, TRUE)) {
$value = '0';
return TRUE;
}
node_import_input_error(t('Input error: %value is not allowed for %name (not a boolean).', array('%value' => $value, '%name' => $field['title'])));
return FALSE;
}
/**
* Check if the value is a valid date.
*
* Uses: $field['output_format'] (output format type - defaults to DATE_UNIX).
* Uses: $options['date_format'], $options['date_custom'] and $options['timezone'] (default to date_default_timezone_name()).
*/
function node_import_check_date(&$value, $field, $options, $preview) {
$timezone = isset($options['timezone']) ? $options['timezone'] : date_default_timezone_name();
$input_format = $options['date_format'] == 'custom' ? $options['date_custom'] : $options['date_format'];
$output_format = isset($field['output_format']) ? $field['output_format'] : DATE_UNIX;
if (date_is_valid($value, DATE_ISO)) {
$value = date_convert($value, DATE_ISO, $output_format, $timezone);
return TRUE;
}
module_load_include('inc', 'date_api', 'date_api_elements');
if (($date = date_convert_from_custom($value, $input_format))) {
// It is useless to check for date_is_valid() as it is a DATE_DATETIME already.
$value = date_convert($date, DATE_DATETIME, $output_format, $timezone);
return TRUE;
}
if (date_is_valid($value, DATE_UNIX)) {
$value = date_convert($value, DATE_UNIX, $output_format, $timezone);
return TRUE;
}
node_import_input_error(t('Input error: %value is not allowed for %name (not a date in %date format).', array('%value' => $value, '%name' => $field['title'], '%date' => format_date(time(), 'custom', $input_format))));
return FALSE;
}
/**
* Check if the value is a valid email address.
*
* Uses: nothing.
*/
function node_import_check_email(&$value, $field, $options, $preview) {
if (!valid_email_address($value)) {
node_import_input_error(t('Input error: %value is not a valid e-mail address.', array('%value' => $value)));
return FALSE;
}
return TRUE;
}
/**
* Check if the value points to a valid filepath.
*
* Uses: $field['to_directory'], $options['from_directory'], $options['manually_moved'].
*/
function node_import_check_filepath(&$value, $field, $options, $preview) {
// No need to check empty values.
if (drupal_strlen($value) == 0) {
return TRUE;
}
// Where should be file be located?
$find_in = isset($options['from_directory']) ? $options['from_directory'] : '';
$find_in = node_import_directory() . (strlen($find_in) > 0 ? '/'. $find_in : '');
if (isset($options['manually_moved']) && $options['manually_moved']) {
$find_in = isset($field['to_directory']) ? $field['to_directory'] : '';
}
$find_in = file_create_path($find_in);
// Check if the file exists.
$filepath = $find_in .'/'. $value;
if (file_check_location($filepath, $find_in) && file_exists($filepath)) {
$value = $filepath;
return TRUE;
}
node_import_input_error(t('Input error: %value is not allowed for %name (not a file in %path).', array('%value' => $value, '%name' => $field['title'], '%path' => $find_in)));
$value = '';
return FALSE;
}
/**
* Check if the value is a valid node reference (by nid or title).
*
* Uses: $field['output_format']. Either 'nid' (default) or 'title'.
*/
function node_import_check_node_reference(&$value, $field, $options, $preview) {
if (($nid = node_import_get_object('node', $value)) !== NULL ||
($nid = db_result(db_query("SELECT nid FROM {node} WHERE nid = %d OR LOWER(title) = '%s' LIMIT 1", is_numeric($value) && intval($value) > 0 ? $value : -1, drupal_strtolower($value))))) {
node_import_set_object('node', $value, $nid);
$value = $nid;
$field['output_format'] = isset($field['output_format']) ? $field['output_format'] : 'nid';
switch ($field['output_format']) {
case 'title':
if (($title = node_import_get_object('node:title', $nid)) ||
($title = db_result(db_query("SELECT title FROM {node} WHERE nid = %d LIMIT 1", $nid)))) {
$value = $title;
node_import_set_object('node:title', $nid, $title);
}
break;
case 'nid':
default:
break;
}
return TRUE;
}
node_import_input_error(t('Input error: %value is not allowed for %name (not a node reference).', array('%value' => $value, '%name' => $field['title'])));
return FALSE;
}
/**
* Check if the value is a valid user (by uid, name or email).
*
* Uses: $field['output_format']. Either 'uid' (default), 'name' or 'email'.
*/
function node_import_check_user_reference(&$value, $field, $options, $preview) {
if (($uid = node_import_get_object('user', $value)) !== NULL ||
($uid = db_result(db_query("SELECT uid FROM {users} WHERE uid = %d OR LOWER(name) = '%s' OR LOWER(mail) = '%s' LIMIT 1", is_numeric($value) && intval($value) > 0 ? $value : -1, drupal_strtolower($value), drupal_strtolower($value))))) {
node_import_set_object('user', $value, $uid);
$value = $uid;
$field['output_format'] = isset($field['output_format']) ? $field['output_format'] : 'uid';
switch ($field['output_format']) {
case 'name':
if (($name = node_import_get_object('user:name', $uid)) ||
($name = db_result(db_query("SELECT name FROM {users} WHERE uid = %d LIMIT 1", $uid)))) {
$value = $name;
node_import_set_object('user:name', $uid, $name);
}
break;
case 'email':
if (($email = node_import_get_object('user:email', $uid)) ||
($email = db_result(db_query("SELECT mail FROM {users} WHERE uid = %d LIMIT 1", $uid)))) {
$value = $email;
node_import_set_object('user:email', $uid, $email);
}
break;
case 'uid':
default:
break;
}
return TRUE;
}
node_import_input_error(t('Input error: %value is not allowed for %name (not an user).', array('%value' => $value, '%name' => $field['title'])));
return FALSE;
}
/**
* Check if the value is in the list of allowed values (by key or value).
*
* Uses: $field['allowed_values'].
*/
function node_import_check_values(&$value, $field, $options, $preview) {
foreach ($field['allowed_values'] as $key => $title) {
$tmp = drupal_strtolower($value);
if ($tmp === drupal_strtolower($key) || $tmp === drupal_strtolower($title)) {
$value = (string)$key;
return TRUE;
}
}
node_import_input_error(t('Input error: %value is not allowed for %name (not in allowed values list).', array('%value' => $value, '%name' => $field['title'])));
return FALSE;
}
/**
* Check if the value is a valid weight (integer between -X and X).
*
* Uses: $field['delta'].
*/
function node_import_check_weight(&$value, $field, $options, $preview) {
$weight = isset($field['delta']) ? $field['delta'] : 10;
if (is_numeric($value) && intval($value) <= $weight && intval($value) >= -$weight) {
$value = intval($value);
return TRUE;
}
node_import_input_error(t('Input error: %value is not allowed for %name (not a weight).', array('%value' => $value, '%name' => $field['title'])));
return FALSE;
}
/**
* @}
*/
/**
* @defgroup node_import_util Various node import utility functions.
* @{
*/
/**
* Store an object-id in the node_import cache.
*
* As some string->object-id lookups can be expensive (in db queries) and
* most of the time the same strings are looked up (eg users), we have a
* cache of object-ids we already have looked up.
*
* This function is used to store an object-id in the cache.
*
* @param $type
* String. The type of object (eg 'user').
*
* @param $value
* String or array. The value we haved looked up.
*
* @param $oid
* Integer. The looked-up object-id. If NULL, the currently stored
* object-id is returned without setting the $type/$value to NULL.
* If you want to reset (eg make it NULL) a value, use
* @code
* node_import_set_object($type, $value, NULL, TRUE);
* @endcode
*
* @param $reset
* Boolean. Whether to reset the cache. If $type is NULL, the whole
* cache is reset. If $value is not NULL only the cache for that
* specific $type/$value is reset.
*
* @return
* Integer. The looked-up object-id. NULL if not found.
*
* @see node_import_get_object().
*/
function node_import_set_object($type, $value, $oid = NULL, $reset = FALSE) {
static $cache;
if (!isset($cache)) {
$cache = array();
}
if (isset($type) && !isset($cache[$type])) {
$cache[$type] = array();
}
$stored_value = NULL;
if (isset($value)) {
$stored_value = is_array($value) ? implode("\n", array_map('drupal_strtolower', $value)) : drupal_strtolower($value);
}
if ($reset) {
if (isset($type)) {
if (isset($value)) {
unset($cache[$type][$stored_value]);
}
else {
$cache[$type] = array();
}
}
else {
$cache = array();
}
return;
}
if (isset($oid)) {
$cache[$type][$stored_value] = $oid;
}
return isset($cache[$type][$stored_value]) ? $cache[$type][$stored_value] : NULL;
}
/**
* Get an object-id from the node_import cache.
*
* @param $type
* String. The type of object (eg 'user').
*
* @param $value
* String or array. The value to get the object-id of.
*
* @return
* NULL if not yet in cache. Otherwise an integer.
*
* @see node_import_set_object().
*/
function node_import_get_object($type, $value) {
return node_import_set_object($type, $value);
}
/**
* Get a property from each element of an array.
*
* @param $array
* Array of ($key => $info).
*
* @param $property
* String.
*
* @return
* Array of ($key => $info->$property).
*/
function node_import_extract_property($array, $property = 'title') {
$result = array();
foreach ((array)$array as $key => $info) {
if (is_array($info) || is_object($info)) {
$info = (array)$info;
$result[$key] = isset($info[$property]) ? $info[$property] : '';
}
else {
$result[$key] = $info;
}
}
return $result;
}
/**
* Function used by uasort to sort structured arrays by weight.
*/
function node_import_sort($a, $b) {
$a_weight = (is_array($a) && isset($a['weight'])) ? $a['weight'] : 0;
$b_weight = (is_array($b) && isset($b['weight'])) ? $b['weight'] : 0;
if ($a_weight == $b_weight) {
return 0;
}
return ($a_weight < $b_weight) ? -1 : 1;
}
/**
* Returns the number of columns given $fieldname is mapped
* to.
*
* @param $fieldname
* String.
*
* @param $map
* Array of file column mapping.
*
* @return
* Integer. Number of file columns the field is mapped to.
*/
function node_import_field_map_count($fieldname, $map) {
if (!isset($map[$fieldname])) {
return 0;
}
if (!is_array($map[$fieldname])) {
return strlen($map[$fieldname]) > 0;
}
$count = 0;
foreach ($map[$fieldname] as $col) {
if ($col !== '') {
$count++;
}
}
return $count;
}
/**
* Set an error on a random form element.
*/
function node_import_input_error($message, $args = array()) {
static $count = 0;
form_set_error('node_import-'. $count, $message, $args);
$count++;
}
/**
* @}
*/
/**
* @defgroup node_import_files Node import file functions
* @{
*/
/**
* Return the full path where files to import are stored.
*
* @param $reset
* Boolean. Reset internal cache.
*
* @return
* String. The directory is created if needed.
*/
function node_import_directory($reset = FALSE) {
static $path;
if (!isset($path) || $reset) {
$path = variable_get('node_import:directory', 'imports');
if (function_exists('token_replace')) {
global $user;
$path = token_replace($path, 'user', user_load($user->uid));
}
if (strlen($path) > 0) {
$parts = array_filter(explode('/', $path));
$path = '';
while (!empty($parts)) {
$path .= array_shift($parts) . '/';
file_check_directory(file_create_path($path), FILE_CREATE_DIRECTORY, 'node_import:directory');
}
}
$path = rtrim($path, '/');
$path = file_create_path($path);
}
return $path;
}
/**
* Returns a list of available files.
*/
function node_import_list_files($reset = FALSE) {
global $user;
static $files;
if (!isset($files) || $reset) {
$files = array();
$path = node_import_directory();
// If FTP uploads of files is allowed, rescan the directory.
if (variable_get('node_import:ftp:enabled', 0)) {
$ftp_user = user_load(array('name' => variable_get('node_import:ftp:user', '')));
//TODO: use the $validators functionality of file_save_upload() ?
$extensions = array_map('drupal_strtolower', array_filter(explode(' ', variable_get('node_import:ftp:extensions', 'csv tsv txt'))));
foreach ($extensions as $extension) {
$extensions[] = drupal_strtoupper($extension);
}
if (!empty($extensions)) {
$existing_files = array();
$result = db_query("SELECT filepath FROM {files} WHERE filepath LIKE '%s%%' AND status = %d", $path, FILE_STATUS_PERMANENT);
while (($file = db_fetch_object($result))) {
$existing_files[$file->filepath] = TRUE;
}
foreach (file_scan_directory($path, '.*\.(('. implode(')|(', $extensions) .'))') as $filename => $file) {
if (!isset($existing_files[$file->filename])) {
$record = (object)array(
'uid' => $ftp_user->uid,
'filename' => $file->basename,
'filepath' => $file->filename,
'filemime' => 'text/plain', //TODO: how to get real MIME?
'filesize' => filesize($file->filename),
'status' => FILE_STATUS_PERMANENT,
'timestamp' => time(),
);
drupal_write_record('files', $record);
drupal_set_message(t('A new file %name was detected in %path.', array('%name' => $record->filename, '%path' => $path)));
}
}
}
}
// Users with 'administer imports' permission can see all files.
//TODO: we should also filter out files that are already in use by another task.
if (user_access('administer imports')) {
$result = db_query("SELECT * FROM {files} WHERE filepath LIKE '%s%%' AND status = %d ORDER BY filename, timestamp", $path, FILE_STATUS_PERMANENT);
}
else {
$result = db_query("SELECT * FROM {files} WHERE filepath LIKE '%s%%' AND (uid = %d OR uid = 0) AND status = %d ORDER BY filename, timestamp", $path, $user->uid, FILE_STATUS_PERMANENT);
}
// Create the list, keep only the still existing files.
while (($file = db_fetch_object($result))) {
if (!file_exists(file_create_path($file->filepath))) {
drupal_set_message(t('The previously uploaded file %name is no longer present. Removing it from the list of uploaded files.', array('%name' => $file->filepath)));
file_set_status($file, FILE_STATUS_TEMPORARY);
}
else {
$files[$file->fid] = $file;
}
}
}
return $files;
}
/**
* Return an autodetected mapping for given headers and content type.
*
* The automapping is done by checking the column titles in the file,
* whether they match with the field name or field title.
*
* @param $type
* String. The node_import type.
*
* @param $headers
* Array of column titles in the file.
*
* @return
* Array of mapping.
*/
function node_import_automap($type, $headers) {
if (user_access('administer imports')) {
$result = db_query("SELECT map FROM {node_import_tasks} WHERE type = '%s' AND LOWER(headers) = '%s' ORDER BY created DESC LIMIT 1", $type, strtolower(serialize($headers)));
}
else {
$result = db_query("SELECT map FROM {node_import_tasks} WHERE type = '%s' AND LOWER(headers) = '%s' AND uid = %d ORDER BY created DESC LIMIT 1", $type, strtolower(serialize($headers)), $user->uid);
}
if (($map = db_result($result))) {
return unserialize($map);
}
$map = array();
$headers = array_map('drupal_strtolower', $headers);
foreach (node_import_fields($type) as $fieldname => $fieldinfo) {
if ($fieldinfo['is_mappable']) {
$map[$fieldname] = '';
if (($col = array_search(drupal_strtolower($fieldname), $headers)) !== FALSE
|| ($col = array_search(drupal_strtolower($fieldinfo['title']), $headers)) !== FALSE) {
$map[$fieldname] = $col;
}
}
}
return $map;
}
/**
* Return an autodetected file format and file options for given
* file.
*
* @param $filepath
* String. Path to file.
*
* @return
* Array of file options.
*/
function node_import_autodetect($filepath) {
//TODO: really implement this.
$file_formats = node_import_format_options('file formats');
return $file_formats['csv'] + array('file_format' => 'csv');
}
/**
* Returns one record from the file starting at file offset using
* the supplied file options.
*
* @param $filepath
* String. Path to file.
*
* @param $file_offset
* Integer. Starting point of record.
*
* @param $file_options
* Array with 'record separator', 'field separator', 'text delimiter'
* and 'escape character'. If not set, the options default to the
* CSV options ("\n", ',', '"', '"').
*
* @return
* Array ($file_offset, $record). The $file_offset is the start offset
* of the next record. The $record is an array of fields (strings).
*
* On error or when the end of the file has been reached we return
* FALSE.
*/
function node_import_read_from_file($filepath, $file_offset, $file_options) {
// Open file and set to file offset.
if (($fp = fopen($filepath, 'r')) === FALSE) {
return FALSE;
}
if (fseek($fp, $file_offset)) {
return FALSE;
}
// File options.
_node_import_sanitize_file_options($file_options);
$rs = $file_options['record separator'];
$fs = $file_options['field separator'];
$td = $file_options['text delimiter'];
$ec = $file_options['escape character'];
// The current record is stored in the $fields array. The $new_offset
// contains the file position of the end of the returned record. Note
// that if $new_offset == $file_offset we have reached the end of the
// file.
$fields = array();
$new_offset = $file_offset;
$start = 0;
// We read $length bytes at a time in the $buffer.
$length = variable_get('node_import:fgets:length', 1024);
$buffer = fgets($fp, $length);
// A field can be enclosed in text delimiters or not. If this variable is
// TRUE, we need to parse until we find the next unescaped text delimiter.
// If FALSE, the field value was not enclosed.
$enclosed = FALSE;
// Read until the EOF or until end of record.
while (!feof($fp) || $start < strlen($buffer)) {
if (!$enclosed) {
// Find the next record separator, field separator and text delimiter.
if ($rs === "\n") {
$pos_rs = strpos($buffer, "\n", $start);
$pos_rs = $pos_rs !== FALSE ? $pos_rs : strpos($buffer, "\r", $start);
}
else {
$pos_rs = strpos($buffer, $rs, $start);
}
$pos_fs = strpos($buffer, $fs, $start);
$pos_td = strlen($td) > 0 ? strpos($buffer, $td, $start) : FALSE;
// Check for begin of text delimited field.
if ($pos_td !== FALSE && ($pos_rs === FALSE || $pos_td <= $pos_rs) && ($pos_fs === FALSE || $pos_td <= $pos_fs)) {
$enclosed = TRUE;
$buffer = substr($buffer, 0, $pos_td) . substr($buffer, $pos_td + strlen($td));
$new_offset += strlen($td);
$start = $pos_td;
continue;
}
// Check for end of record.
if ($pos_rs !== FALSE && ($pos_fs === FALSE || $pos_rs <= $pos_fs)) {
if ($pos_rs >= 0) {
$fields[] = substr($buffer, 0, $pos_rs);
$buffer = '';
$new_offset += $pos_rs;
$start = 0;
}
else if (empty($fields)) {
$buffer = substr($buffer, strlen($rs), strlen($buffer) - strlen($rs));
$new_offset += strlen($rs);
$start = 0;
continue;
}
$new_offset += strlen($rs);
break;
}
// Check for end of field.
if ($pos_fs !== FALSE) {
$fields[] = substr($buffer, 0, $pos_fs);
$buffer = substr($buffer, $pos_fs + strlen($fs));
$new_offset += $pos_fs + strlen($fs);
$start = 0;
continue;
}
}
else {
// Find the next text delimiter and escaped text delimiter.
$pos_td = strpos($buffer, $td, $start);
$pos_ec = strlen($ec) > 0 ? strpos($buffer, $ec . $td, $start) : FALSE;
// Check for end of text delimited field.
if ($pos_td !== FALSE && ($pos_ec === FALSE || $pos_td <= ($pos_ec - strlen($td)))) {
$enclosed = FALSE;
$buffer = substr($buffer, 0, $pos_td) . substr($buffer, $pos_td + strlen($td));
$new_offset += strlen($td);
$start = $pos_td;
continue;
}
// Check for escaped text delimiter.
if ($pos_ec !== FALSE) {
$buffer = substr($buffer, 0, $pos_ec) . substr($buffer, $pos_ec + strlen($ec));
$new_offset += strlen($ec);
$start = $pos_ec + strlen($td);
continue;
}
}
// Nothing found... read more data.
$start = strlen($buffer);
$buffer .= fgets($fp, $length);
}
// Check if we need to add the last field.
if (feof($fp) && strlen($buffer) > 0) {
$fields[] = $buffer;
$new_offset += strlen($buffer);
}
// Remove extra white space.
$fields = array_map('trim', $fields);
// Check whether the whole row is empty.
$empty_row = TRUE;
foreach ($fields as $field) {
if (strlen($field) > 0) {
$empty_row = FALSE;
break;
}
}
if ($empty_row && !feof($fp) && !empty($fields)) {
return node_import_read_from_file($filepath, $new_offset, $file_options);
}
// Cleanup and return.
$result = (!feof($fp) || !empty($fields)) ? array($new_offset, $fields) : FALSE;
unset($buffer);
fclose($fp);
return $result;
}
function _node_import_sanitize_file_options(&$file_options) {
// File options.
$replaces = array('' => "\n", '' => "\t", '' => '');
$options = array(
'record separator' => '',
'field separator' => ',',
'text delimiter' => '"',
'escape character' => '"',
);
foreach ($options as $key => $default) {
if (isset($file_options[$key]) && strlen($file_options[$key]) > 0) {
$options[$key] = $file_options[$key];
}
else if (isset($file_options['other '. $key]) && strlen($file_options['other '. $key]) > 0) {
$options[$key] = $file_options['other '. $key];
}
}
$file_options = str_replace(array_keys($replaces), array_values($replaces), $options);
}
/**
* Returns one line in the specified file format of the array of
* values.
*
* @param $values
* Array of strings.
*
* @param $file_options
* Array with 'record separator', 'field separator', 'text delimiter'
* and 'escape character'. If not set, the options default to the
* CSV options ("\n", ',', '"', '"').
*
* @return
* String.
*/
function node_import_write_to_string($values, $file_options) {
// File options.
_node_import_sanitize_file_options($file_options);
$rs = $file_options['record separator'];
$fs = $file_options['field separator'];
$td = $file_options['text delimiter'];
$ec = $file_options['escape character'];
// Write data.
$output = '';
if (is_array($values) && !empty($values)) {
// TODO: we could avoid writing $td if the $value does not contain $td, $fs or $rs.
if (drupal_strlen($td) > 0) {
foreach ($values as $i => $value) {
$values[$i] = $td . str_replace($td, $ec . $td, $value) . $td;
}
}
$output = implode($fs, $values);
}
return $output . $rs;
}
/**
* @}
*/
/**
* @defgroup node_import_form_hacks Hacks for includes/form.inc
* @{
* The problem node_import has by design - where the design is that we
* want to use the normal form validation - is that when a form is
* submitted more then once, the validation of the form is not done
* correctly by the core includes/form.inc
*
* This file tries to lift this limitation. The solution is based (or
* rather copied) from sites/all/modules/views/includes/form.inc of
* the views module which needs to do the same crappy stuff (and even
* more).
*
* Short explanation: instead of
* @code
* drupal_execute($form_id, $form_state, ...);
* @endcode
* use
* @code
* node_import_drupal_execute($form_id, $form_state, ...);
* @endcode
* whenever the form you want to execute can be executed more than
* once in the same page request.
*
* For the core bug, see http://drupal.org/node/260934 : Static caching:
* cannot call drupal_validate_form on the same form more than once.
*
* Note that another bug for multiple form validation and submission
* (one that could not be lifted) was fixed in Drupal 6.5, see
* http://drupal.org/node/180063 : No way to flush form errors during
* iterative programatic form submission.
*
* Many, many thanks to merlinofchaos!!
*/
/**
* The original version of drupal_execute() calls drupal_process_form().
* The modified version sets $form_state['must_validate'] = TRUE and
* calls node_import_drupal_process_form() instead.
*/
function node_import_drupal_execute($form_id, &$form_state) {
$args = func_get_args();
$args[1] = &$form_state;
$form = call_user_func_array('drupal_retrieve_form', $args);
$form['#post'] = $form_state['values'];
$form_state['must_validate'] = TRUE;
drupal_prepare_form($form_id, $form, $form_state);
node_import_drupal_process_form($form_id, $form, $form_state);
}
/**
* The original version of drupal_process_form() calls drupal_validate_form().
* The modified version calls node_import_drupal_validate_form() instead.
*/
function node_import_drupal_process_form($form_id, &$form, &$form_state) {
$form_state['values'] = array();
$form = form_builder($form_id, $form, $form_state);
// Only process the form if it is programmed or the form_id coming
// from the POST data is set and matches the current form_id.
if ((!empty($form['#programmed'])) || (!empty($form['#post']) && (isset($form['#post']['form_id']) && ($form['#post']['form_id'] == $form_id)))) {
node_import_drupal_validate_form($form_id, $form, $form_state);
// form_clean_id() maintains a cache of element IDs it has seen,
// so it can prevent duplicates. We want to be sure we reset that
// cache when a form is processed, so scenerios that result in
// the form being built behind the scenes and again for the
// browser don't increment all the element IDs needlessly.
form_clean_id(NULL, TRUE);
if ((!empty($form_state['submitted'])) && !form_get_errors() && empty($form_state['rebuild'])) {
$form_state['redirect'] = NULL;
form_execute_handlers('submit', $form, $form_state);
// We'll clear out the cached copies of the form and its stored data
// here, as we've finished with them. The in-memory copies are still
// here, though.
if (variable_get('cache', CACHE_DISABLED) == CACHE_DISABLED && !empty($form_state['values']['form_build_id'])) {
cache_clear_all('form_'. $form_state['values']['form_build_id'], 'cache_form');
cache_clear_all('storage_'. $form_state['values']['form_build_id'], 'cache_form');
}
// If batches were set in the submit handlers, we process them now,
// possibly ending execution. We make sure we do not react to the batch
// that is already being processed (if a batch operation performs a
// drupal_execute).
if ($batch =& batch_get() && !isset($batch['current_set'])) {
// The batch uses its own copies of $form and $form_state for
// late execution of submit handers and post-batch redirection.
$batch['form'] = $form;
$batch['form_state'] = $form_state;
$batch['progressive'] = !$form['#programmed'];
batch_process();
// Execution continues only for programmatic forms.
// For 'regular' forms, we get redirected to the batch processing
// page. Form redirection will be handled in _batch_finished(),
// after the batch is processed.
}
// If no submit handlers have populated the $form_state['storage']
// bundle, and the $form_state['rebuild'] flag has not been set,
// we're finished and should redirect to a new destination page
// if one has been set (and a fresh, unpopulated copy of the form
// if one hasn't). If the form was called by drupal_execute(),
// however, we'll skip this and let the calling function examine
// the resulting $form_state bundle itself.
if (!$form['#programmed'] && empty($form_state['rebuild']) && empty($form_state['storage'])) {
drupal_redirect_form($form, $form_state['redirect']);
}
}
}
}
/**
* The original version of drupal_validate_form() keeps a static array
* of validated forms. The modified version checks $form_state['must_validate']
* to see if the form needs validation. If set and TRUE, validation is
* forced even if it was already done.
*/
function node_import_drupal_validate_form($form_id, $form, &$form_state) {
static $validated_forms = array();
if (isset($validated_forms[$form_id]) &&
(!isset($form_state['must_validate']) || $form_state['must_validate'] !== TRUE)) { //Changed!
return;
}
// If the session token was set by drupal_prepare_form(), ensure that it
// matches the current user's session.
if (isset($form['#token'])) {
if (!drupal_valid_token($form_state['values']['form_token'], $form['#token'])) {
// Setting this error will cause the form to fail validation.
form_set_error('form_token', t('Validation error, please try again. If this error persists, please contact the site administrator.'));
}
}
_form_validate($form, $form_state, $form_id);
$validated_forms[$form_id] = TRUE;
}
/**
* @}
*/
/**
* @defgroup node_import_locking Node import locking functions
* @{
* Locking functions for node_import. This code is based on #251792
* (Implement a locking framework for long operations). We need
* locking to avoid both hook_cron() and node_import_view_form() to
* process the same task at the same time (which would result in
* rows being processed twice).
*
* The code below uses the database-based locking mechanism. Note
* that is does not use the full implementation as in the patch
* because we can have only one global lock. One lock for each task
* does not work because a task can spawn the creation of something
* else (such as a taxonomy term).
*
* We only need one _acquire() and _release() function.
*
* When a more general locking framework is committed to Drupal,
* we can easily replace this.
*/
/**
* Acquire or release our node_import lock.
*
* @param $release
* Boolean. If TRUE, release the lock. If FALSE, acquire the
* lock.
*
* @return
* Boolean. Whether the lock was acquired.
*/
function node_import_lock_acquire($release = FALSE) {
static $lock_id, $locked;
if (!isset($lock_id)) {
$lock_id = md5(uniqid());
$locked = FALSE;
register_shutdown_function('node_import_lock_release');
}
if ($release) {
db_query("DELETE FROM {variable} WHERE name = '%s'", 'node_import:lock');
$locked = FALSE;
}
else if (!$locked) {
if (@db_query("INSERT INTO {variable} (name, value) VALUES ('%s', '%s')", 'node_import:lock', $lock_id)) {
$locked = TRUE;
}
}
return $locked;
}
/**
* Release our node_import lock.
*
* @return
* Nothing.
*/
function node_import_lock_release() {
node_import_lock_acquire(TRUE);
}
/**
* @}
*/