You are here

feed_import.module in Feed Import 7.2

Same filename and directory in other branches
  1. 8 feed_import.module
  2. 7.3 feed_import.module
  3. 7 feed_import.module

User interface, cron functions for feed_import module

File

feed_import.module
View source
<?php

/**
 * @file
 * User interface, cron functions for feed_import module
 */

/**
 * This is path to feed import UI
 */
define('FEED_IMPORT_PATH', 'admin/config/services/feed_import');

/**
 * Implements hook_help().
 */
function feed_import_help($path, $arg) {
  if ($path == 'admin/help#feed_import') {
    $vars = array(
      '!file' => l('README.txt', drupal_get_path('module', 'feed_import') . '/README.txt'),
      '!project_page' => l('Feed Import', 'http://drupal.org/project/feed_import'),
    );
    $help = t('Import content into entities from various file types (XML, HTML, CSV, ...) using XPATH.');
    $help .= '<br />';
    $help .= t('For more info please read !file file or go to !project_page.', $vars);
    return $help;
  }
}

/**
 * Implements hook_permision().
 */
function feed_import_permission() {
  return array(
    'feed import' => array(
      'title' => t('Administer feed import settings'),
      'description' => t('Change settings for feed import'),
    ),
    'feed import process' => array(
      'title' => t('Administer feed import settings'),
      'description' => t('Import a feed'),
    ),
  );
}

/**
 * Implements hook_menu().
 */
function feed_import_menu() {

  // FEED_IMPORT_PATH is defined at the top of this file.
  // This is used for arguments to know the position.
  $submenus = substr_count(FEED_IMPORT_PATH, '/');
  $items = array();
  $items[FEED_IMPORT_PATH] = array(
    'title' => 'Feed Import',
    'description' => 'Configure feed import',
    'page callback' => 'feed_import_list_feeds',
    'access callback' => 'user_access',
    'access arguments' => array(
      'feed import',
    ),
    'type' => MENU_NORMAL_ITEM,
  );
  $items[FEED_IMPORT_PATH . '/add'] = array(
    'title' => 'Add new feed',
    'description' => 'Add a new feed',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'feed_import_add_new_feed_form',
    ),
    'access callback' => 'user_access',
    'access arguments' => array(
      'feed import',
    ),
    'type' => MENU_LOCAL_ACTION,
    'weight' => 1,
  );
  $items[FEED_IMPORT_PATH . '/import'] = array(
    'title' => 'Import feed',
    'description' => 'Import an existing feed',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'feed_import_import_feed_form',
    ),
    'access callback' => 'user_access',
    'access arguments' => array(
      'feed import',
    ),
    'type' => MENU_LOCAL_ACTION,
    'weight' => 2,
  );
  $items[FEED_IMPORT_PATH . '/export/all'] = array(
    'title' => 'Export all feeds',
    'description' => 'Exports all feeds',
    'page callback' => 'feed_import_export_feed_page',
    'page arguments' => array(
      $submenus + 2,
    ),
    'access callback' => 'user_access',
    'access arguments' => array(
      'feed import',
    ),
    'type' => MENU_LOCAL_ACTION,
    'weight' => 3,
  );
  $items[FEED_IMPORT_PATH . '/settings'] = array(
    'title' => 'Settings',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'feed_import_settings_form',
    ),
    'access callback' => 'user_access',
    'access arguments' => array(
      'feed import',
    ),
    'type' => MENU_LOCAL_ACTION,
    'weight' => 4,
  );
  $items[FEED_IMPORT_PATH . '/export/%'] = array(
    'title' => 'Export feeds',
    'description' => 'Export feeds',
    'page callback' => 'feed_import_export_feed_page',
    'page arguments' => array(
      $submenus + 2,
    ),
    'access callback' => 'user_access',
    'access arguments' => array(
      'feed import',
    ),
    'type' => MENU_CALLBACK,
  );
  $items[FEED_IMPORT_PATH . '/process'] = array(
    'title' => 'Process feed',
    'page callback' => 'feed_import_process_feed',
    'access callback' => 'user_access',
    'access arguments' => array(
      'feed import process',
    ),
    'type' => MENU_CALLBACK,
  );
  $items[FEED_IMPORT_PATH . '/enable'] = array(
    'title' => 'Enable feed',
    'page callback' => 'feed_import_change_status',
    'page arguments' => array(
      'enable',
    ),
    'access callback' => 'user_access',
    'access arguments' => array(
      'feed import',
    ),
    'type' => MENU_CALLBACK,
  );
  $items[FEED_IMPORT_PATH . '/disable'] = array(
    'title' => 'Disable feed',
    'page callback' => 'feed_import_change_status',
    'page arguments' => array(
      'disable',
    ),
    'access callback' => 'user_access',
    'access arguments' => array(
      'feed import',
    ),
    'type' => MENU_CALLBACK,
  );
  $items[FEED_IMPORT_PATH . '/delete'] = array(
    'title' => 'Delete feed',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'feed_import_delete_feed_form',
    ),
    'access callback' => 'user_access',
    'access arguments' => array(
      'feed import',
    ),
    'type' => MENU_CALLBACK,
  );
  $items[FEED_IMPORT_PATH . '/edit/%/feed'] = array(
    'title' => 'Edit feed',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'feed_import_edit_feed_form',
      $submenus + 2,
    ),
    'access callback' => 'user_access',
    'access arguments' => array(
      'feed import',
    ),
    'type' => MENU_LOCAL_TASK,
    'tab_root' => FEED_IMPORT_PATH . '/edit/%',
    'weight' => 1,
  );
  $items[FEED_IMPORT_PATH . '/edit/%/re-order'] = array(
    'title' => 'Re-order fields',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'feed_import_reorder_fields_form',
      $submenus + 2,
    ),
    'access callback' => 'user_access',
    'access arguments' => array(
      'feed import',
    ),
    'type' => MENU_LOCAL_TASK,
    'tab_root' => FEED_IMPORT_PATH . '/edit/%',
    'weight' => 2,
  );
  $items[FEED_IMPORT_PATH . '/edit/%/pre-filter'] = array(
    'title' => 'Edit pre-filters',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'feed_import_edit_filter_form',
      '#pre_filter',
      $submenus + 2,
    ),
    'access callback' => 'user_access',
    'access arguments' => array(
      'feed import',
    ),
    'type' => MENU_LOCAL_TASK,
    'tab_root' => FEED_IMPORT_PATH . '/edit/%',
    'weight' => 3,
  );
  $items[FEED_IMPORT_PATH . '/edit/%/filter'] = array(
    'title' => 'Edit filters',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'feed_import_edit_filter_form',
      '#filter',
      $submenus + 2,
    ),
    'access callback' => 'user_access',
    'access arguments' => array(
      'feed import',
    ),
    'type' => MENU_LOCAL_TASK,
    'tab_root' => FEED_IMPORT_PATH . '/edit/%',
    'weight' => 4,
  );
  return $items;
}

/**
 * Implements hook_feed_import_process_info().
 */
function feed_import_feed_import_process_info() {

  // Return default process functions located in FeedImport class.
  $processXmlSettings = array();
  for ($i = 0; $i < 5; $i++) {
    $processXmlSettings['namespace_' . $i] = array(
      'title' => t('Namespace') . ' ' . ($i + 1),
      'description' => '',
      'default' => '',
    );
  }
  return array(
    'processXML' => array(
      'info' => t('Set namespaces with format: name http://example.com/namespace. This may slow down import.'),
      'function' => array(
        'FeedImport',
        'processXML',
      ),
      'settings' => $processXmlSettings,
      'validate' => array(
        'FeedImportFilter',
        'processXMLValidate',
      ),
    ),
    'processXMLChunked' => array(
      'info' => t('Set xml properties and chunk size.'),
      'function' => array(
        'FeedImport',
        'processXMLChunked',
      ),
      'settings' => array(
        'items_count' => array(
          'title' => t('Save entities when this number of imported items is reached'),
          'description' => t('This is used to keep memory in a stable range and not exceed it.'),
          'default' => 1000,
        ),
        'xml_properties' => array(
          'title' => t('XML properties'),
          'description' => t('Change xml properties like encoding.'),
          'default' => '<?xml version="1.0" encoding="utf-8"?>',
        ),
        'chunk_size' => array(
          'title' => t('Chunk size in bytes'),
          'description' => t('How many bytes to read in each chunk.'),
          'default' => 8192,
        ),
        'substr_function' => array(
          'title' => t('Substring function'),
          'description' => t('What function to use for substring: substr, mb_substr or drupal_substr.'),
          'default' => 'substr',
        ),
      ),
      'validate' => array(
        'FeedImport',
        'processXMLChunkedValidate',
      ),
    ),
    'processHTMLPage' => array(
      'info' => t('You can ignore HTML errors so they will not appear in report.'),
      'function' => array(
        'FeedImport',
        'processHTMLPage',
      ),
      'settings' => array(
        'report_html_errors' => array(
          'title' => t('Report HTML errors'),
          'description' => t('Use 1 to report or 0 to ignore HTML errors.'),
          'default' => 1,
        ),
      ),
      'validate' => array(
        'FeedImport',
        'processHTMLPAgeValidate',
      ),
    ),
    'processCSV' => array(
      'info' => t('For parent xpath use //row') . '<br />' . t("Use column[@index='1'] or column[1] to get first column and use column[@name='age'] to get column 'age' if Use column names is enabled."),
      'function' => array(
        'FeedImport',
        'processCSV',
      ),
      'settings' => array(
        'length' => array(
          'title' => t('Line length'),
          'description' => t('Length must be greater than the longest line in CSV file.') . '<br />' . t('Use 0 if you want to omit this but reading will be slightly slow.'),
          'default' => 0,
        ),
        'delimiter' => array(
          'title' => t('Delimiter character'),
          'description' => t('Set the field delimiter.'),
          'default' => ',',
        ),
        'enclosure' => array(
          'title' => t('Enclosure character'),
          'description' => t('Set the field enclosure character.'),
          'default' => '"',
        ),
        'escape' => array(
          'title' => t('Escape character'),
          'description' => t('Set the escape character.'),
          'default' => '\\',
        ),
        'use_column_names' => array(
          'title' => t('Use column names'),
          'description' => t('Using this, first line will be interpretated as column names and not imported.') . '<br />' . t('Use 1 for YES and 0 for NO.'),
          'default' => 0,
        ),
      ),
      'validate' => array(
        'FeedImport',
        'processCSVValidate',
      ),
    ),
    'processXmlReader' => array(
      'function' => array(
        'FeedImport',
        'processXmlReader',
      ),
      'info' => t('Process xml using XmlReader.'),
      'settings' => array(
        'items_count' => array(
          'title' => t('Save entities when this number of imported items is reached'),
          'description' => t('This is used to keep memory in a stable range and not exceed it.'),
          'default' => 500,
        ),
      ),
      'validate' => array(
        'FeedImport',
        'processXmlReaderValidate',
      ),
    ),
    'processJSON' => array(
      'function' => array(
        'FeedImport',
        'processJSON',
      ),
      'info' => t('Process JSON string.'),
      'settings' => array(
        'xml_properties' => array(
          'title' => t('XML properties'),
          'description' => t('Change xml properties like encoding or root element.'),
          'default' => '<?xml version="1.0" encoding="utf-8"?><root/>',
        ),
      ),
      'validate' => array(
        'FeedImport',
        'processJSONValidate',
      ),
    ),
  );
}

/**
 * Implements hook_entity_delete().
 */
function feed_import_entity_delete($entity, $type) {
  $info = FeedImport::getEntityInfo($type);
  $id = $entity->{$info['column']};
  if ($id && is_numeric($id)) {
    feed_import_add_to_delete($id, $type);
  }
}

/**
 * Schedule an item for removing from feed import items at the end of the script
 *
 * @param int $id
 *   Entity id
 * @param string $type
 *   Entity name
 */
function feed_import_add_to_delete($id = NULL, $type = NULL) {

  // Ids to delete.
  static $ids = array();

  // If delete function is registered at shutdown.
  static $registered = FALSE;
  if (!$registered) {

    // Register shutdown function.
    drupal_register_shutdown_function(__FUNCTION__, NULL);
    $registered = TRUE;
  }

  // NULL means function is called at shutdown so delete items.
  if ($id == NULL) {
    FeedImport::deleteItemsbyEntityId($ids);

    // Reset ids.
    $ids = array();
  }
  else {

    // Just add to entities array an integer id.
    $ids[$type][] = (int) $id;
  }
}

/**
 * Implements hook_cron().
 */
function feed_import_cron() {

  // Check if cron import is enabled.
  if (variable_get('feed_import_use_cron', FALSE)) {

    // Check if there is an already running import or there are no feeds because
    // overlapping an import for the same entity is bad.
    if (!variable_get('feed_import_import_running', FALSE) || variable_get('feed_import_let_overlap', FALSE)) {
      $can_import = FALSE;
      $time_settings = variable_get('feed_import_time_settings', 0);
      if ($time_settings == 1) {

        // Specified interval.
        $time1 = $time2 = 0;
        list($h, $m) = explode(':', variable_get('feed_import_interval_start', '00:00'));
        $time1 = mktime($h, $m, 0);
        list($h, $m) = explode(':', variable_get('feed_import_interval_stop', '00:00'));
        $time2 = mktime($h, $m, 0);
        if ($time1 < $time2) {
          $can_import = $time1 <= REQUEST_TIME && REQUEST_TIME <= $time2;
        }
        unset($time1, $time2, $h, $m);
      }
      else {
        $last_executed = variable_get('feed_import_last_executed_import', 0);
        $time_between = variable_get('feed_import_time_between_imports', 3600);
        $can_import = $last_executed + $time_between < REQUEST_TIME;
      }

      // Check if we can import.
      if ($can_import) {
        $feeds = FeedImport::loadFeeds(TRUE);
        if (!empty($feeds)) {
          $feed_names = array_keys($feeds);
          $last_feed = variable_get('feed_import_last_imported_feed', '');
          if ($last_feed == '') {
            $last_feed = 0;
          }
          else {
            $last_feed = (array_search($last_feed, $feed_names) + 1) % count($feed_names);
          }
          $last_feed = $feed_names[$last_feed];
          $feeds = $feeds[$last_feed];
          variable_set('feed_import_last_imported_feed', $last_feed);
          variable_set('feed_import_last_executed_import', REQUEST_TIME);

          // Mark import as running.
          variable_set('feed_import_import_running', TRUE);

          // Process feed.
          feed_import_import_items($feeds);

          // Change running status.
          variable_set('feed_import_import_running', FALSE);
          unset($feeds, $feed_names, $last_feed);
        }
      }
    }
  }

  // Delete expired items.
  $ids = FeedImport::getExpiredItems(variable_get('feed_import_delete_items_per_cron', 300));
  feed_import_delete_items($ids);
  unset($ids);
}

/**
 * Import feed and set report
 *
 * @param array &$feed
 *   Feed info array
 */
function feed_import_import_items(&$feed) {

  // Process feed.
  FeedImport::processFeed($feed);

  // Get generated report.
  $report = FeedImport::$report;

  // Reset feed report.
  FeedImport::$report = array();

  // Set report message.
  $msg = 'Feed %feed imported.
          Started %started, file downloaded & parsed %parse,
          processing items %process, total duration %time,
          total feed items %total, rescheduled %rescheduled,
          updated %updated, new %new, not imported %skipped.
          !errors';
  if (!empty($report['errors'])) {
    $report['errors'] = array(
      'rows' => $report['errors'],
      'header' => array(
        t('Error'),
        t('Error number'),
        t('Line'),
        t('File'),
      ),
    );
    $report['errors'] = theme('table', $report['errors']);
  }
  else {
    $report['errors'] = '';
  }
  $info = array(
    '%feed' => $feed['name'],
    '%started' => date('d/m/Y H:i:s', $report['start']),
    '%time' => gmdate('H:i:s', $report['time']),
    '%parse' => gmdate('H:i:s', $report['parse']),
    '%process' => gmdate('H:i:s', $report['time'] - $report['parse']),
    '%total' => $report['total'],
    '%rescheduled' => $report['rescheduled'],
    '%updated' => $report['updated'],
    '%new' => $report['new'],
    '%skipped' => $report['total'] - ($report['updated'] + $report['rescheduled'] + $report['new']),
    '!errors' => '<br />' . $report['errors'],
  );
  watchdog('Feed Import', $msg, $info, $report['errors'] ? WATCHDOG_WARNING : WATCHDOG_NOTICE);
}

/**
 * Delete feed items and set report
 *
 * @param array &$items
 *   Array of entity ids keyed by entity type
 */
function feed_import_delete_items(&$items) {
  $start = time();
  $ids = array();

  // Get deleted ids.
  foreach ($items as $type => &$value) {
    $value = FeedImport::entityDelete($type, $value);
    if (!isset($ids[$type])) {
      $ids[$type] = $value;
    }
    else {
      $ids[$type] += $value;
    }
    $value = NULL;
  }
  unset($items);
  if (empty($ids)) {
    return;
  }

  // Delete items from feed_import_hashes.
  FeedImport::deleteItemsbyEntityId($ids);

  // Count deleted items for each entity.
  foreach ($ids as $type => &$id) {
    $id = count($id) . ' ' . $type . ' ' . t('items');
  }
  $ids = implode(', ', $ids);

  // Set report message only if we deleted items.
  $msg = 'Deleted %items. Started %started, duration %time';
  $info = array(
    '%items' => $ids,
    '%started' => date('d/m/Y H:i:s', $start),
    '%time' => gmdate('H:i:s', time() - $start),
  );
  watchdog('Feed Import', $msg, $info);
}

/**
 * Settings form
 */
function feed_import_settings_form($form, &$form_state) {
  $form['feed_import_use_cron'] = array(
    '#type' => 'checkbox',
    '#default_value' => variable_get('feed_import_use_cron', 0),
    '#title' => t('Cron import'),
    '#description' => t('Run import for enabled feeds at cron'),
  );
  $form['container'] = array(
    '#type' => 'fieldset',
    '#states' => array(
      'invisible' => array(
        'input[name="feed_import_use_cron"]' => array(
          'checked' => FALSE,
        ),
      ),
    ),
  );
  $form['container']['feed_import_let_overlap'] = array(
    '#type' => 'checkbox',
    '#default_value' => variable_get('feed_import_let_overlap', 0),
    '#title' => t('Allow import overlap'),
    '#description' => t('This is not indicated for nodes.'),
  );
  $form['container']['feed_import_time_settings'] = array(
    '#type' => 'radios',
    '#options' => array(
      t('From time to time'),
      t('Specified time interval'),
    ),
    '#default_value' => variable_get('feed_import_time_settings', 0),
    '#title' => t('When feeds can be imported'),
  );
  $form['container']['feed_import_time_between_imports'] = array(
    '#type' => 'textfield',
    '#default_value' => variable_get('feed_import_time_between_imports', 3600),
    '#title' => t('Time between two imports at cron (seconds)'),
    '#description' => t('Time betwen two cron imports.'),
    '#states' => array(
      'visible' => array(
        'input[name="feed_import_time_settings"]' => array(
          'value' => 0,
        ),
      ),
    ),
  );
  $form['container']['feed_import_interval_start'] = array(
    '#type' => 'textfield',
    '#default_value' => variable_get('feed_import_interval_start', '00:00'),
    '#title' => t('Start time'),
    '#description' => t('Format is hh:mm.'),
    '#states' => array(
      'visible' => array(
        'input[name="feed_import_time_settings"]' => array(
          'value' => 1,
        ),
      ),
    ),
  );
  $form['container']['feed_import_interval_stop'] = array(
    '#type' => 'textfield',
    '#default_value' => variable_get('feed_import_interval_stop', '24:00'),
    '#title' => t('End time'),
    '#description' => t('Format is hh:mm. This must be greater than start time.'),
    '#states' => array(
      'visible' => array(
        'input[name="feed_import_time_settings"]' => array(
          'value' => 1,
        ),
      ),
    ),
  );
  $form['feed_import_delete_items_per_cron'] = array(
    '#type' => 'textfield',
    '#default_value' => variable_get('feed_import_delete_items_per_cron', 300),
    '#title' => t('Expired items delete per cron'),
    '#description' => t('How many expired items to delete when cron runs.'),
  );
  $form['feed_import_insert_hashes_chunk'] = array(
    '#type' => 'textfield',
    '#default_value' => variable_get('feed_import_insert_hashes_chunk', 500),
    '#title' => t('Chunk size for inserting hashes'),
    '#description' => t('How many items to insert at once'),
  );
  $form['feed_import_update_ids_chunk'] = array(
    '#type' => 'textfield',
    '#default_value' => variable_get('feed_import_update_ids_chunk', 1000),
    '#title' => t('Chunk size for updating expire time'),
    '#description' => t('How many items to update at once'),
  );
  $form['feed_import_hash_property'] = array(
    '#type' => 'textfield',
    '#default_value' => variable_get('feed_import_hash_property', '_feed_item_hash'),
    '#title' => t('Tomporary property which holds item hash'),
    '#description' => t('Change this only if you already have a property named like this.'),
  );
  $form['feed_import_field_param_name'] = array(
    '#type' => 'textfield',
    '#default_value' => variable_get('feed_import_field_param_name', '[field]'),
    '#title' => t('Param name for filters'),
    '#description' => t('Enter this when using filters and you want to send extracted field value as argument.'),
  );
  return system_settings_form($form);
}

/**
 * Settings form validate
 */
function feed_import_settings_form_validate($form, &$form_state) {
  $numeric_fields = array(
    'feed_import_time_between_imports',
    'feed_import_delete_items_per_cron',
    'feed_import_insert_hashes_chunk',
    'feed_import_update_ids_chunk',
  );

  // Checking numeric fields.
  foreach ($numeric_fields as &$field) {
    if (!is_numeric($form_state['values'][$field])) {
      form_error($form[$field], t('Field value must be numeric.'));
    }
  }

  // Checking hash property field (this must be a variable name).
  if (!preg_match("/^[A-Za-z_][A-Za-z0-9_]*\$/", $form_state['values']['feed_import_hash_property'])) {
    form_error($form['feed_import_hash_property'], t('This must be a valid variable name.'));
  }

  // Check interval.
  $ok = TRUE;
  if (!preg_match("/^[0-2][0-9]:[0-5][0-9]\$/", $form_state['values']['feed_import_interval_start'])) {
    form_error($form['container']['feed_import_interval_start'], t('Invalid start time.'));
    $ok = FALSE;
  }
  if (!preg_match("/^[0-2][0-9]:[0-5][0-9]\$/", $form_state['values']['feed_import_interval_stop'])) {
    form_error($form['container']['feed_import_interval_stop'], t('Invalid end time.'));
    $ok = FALSE;
  }
  if ($ok) {
    if ($form_state['values']['feed_import_interval_stop'] < $form_state['values']['feed_import_interval_start']) {
      form_error($form['container']['feed_import_interval_stop'], t('End time must be greater than start time.'));
    }
  }
}

/**
 * List all feeds
 *
 * @return string
 *   A formatted table containing all feeds
 */
function feed_import_list_feeds() {

  // Show a warning about safe mode if is on.
  if (ini_get('safe_mode')) {
    $msg = array(
      '@number' => ini_get('max_execution_time'),
    );
    $msg = t('Looks like safe mode is on, large imports can break if maximum time limit of @number seconds is exceeded.', $msg);
    drupal_set_message($msg, 'warning');
  }

  // Load all feeds.
  $feeds = FeedImport::loadFeeds();
  $rows = array();
  foreach ($feeds as &$feed) {
    $feed['name'] = l($feed['name'], FEED_IMPORT_PATH . '/edit/' . $feed['id'] . '/feed');
    $feed['url'] = l($feed['url'], $feed['url'], array(
      'attributes' => array(
        'target' => '_new',
      ),
    ));
    if ($feed['enabled']) {
      $enabled = TRUE;
      $feed['enabled'] = t('Yes') . ' ' . l('Disable', FEED_IMPORT_PATH . '/disable/' . $feed['id']);
    }
    else {
      $enabled = FALSE;
      $feed['enabled'] = t('No') . ' ' . l('Enable', FEED_IMPORT_PATH . '/enable/' . $feed['id']);
    }

    // Add operations.
    $feed['operations']['edit'] = l(t('Edit'), FEED_IMPORT_PATH . '/edit/' . $feed['id'] . '/feed');
    $feed['operations']['process'] = l(t('Process'), FEED_IMPORT_PATH . '/process/' . $feed['id']);
    $feed['operations']['export'] = l(t('Export'), FEED_IMPORT_PATH . '/export/' . $feed['id']);
    $feed['operations']['delete'] = l(t('Delete'), FEED_IMPORT_PATH . '/delete/' . $feed['id']);
    $rows[] = array(
      'data' => array(
        $feed['name'],
        $feed['url'],
        $feed['enabled'],
        $feed['operations']['edit'],
        $feed['operations']['process'],
        $feed['operations']['export'],
        $feed['operations']['delete'],
      ),
      'class' => array(
        $enabled ? 'enabled-feed' : 'disabled-feed',
      ),
    );
  }

  // Path to CSS file.
  $path = drupal_get_path('module', 'feed_import') . '/feed_import.css';
  return array(
    '#theme' => 'table',
    '#header' => array(
      t('Name'),
      t('Url'),
      t('Cron import'),
      array(
        'data' => t('Operations'),
        'colspan' => 4,
      ),
    ),
    '#rows' => $rows,
    '#empty' => t('There are no feeds.'),
    '#attached' => array(
      'css' => array(
        $path => array(
          'type' => 'file',
        ),
      ),
    ),
  );
}

/**
 * Processes a feed
 *
 * @param int $id
 *   Feed id to process
 */
function feed_import_process_feed($id = 0) {
  $err = FALSE;
  if ($id) {
    $feed = FeedImport::loadFeeds(FALSE, $id);
    if (!empty($feed)) {
      feed_import_import_items($feed);
      drupal_set_message(t('Feed @name processed!', array(
        '@name' => $feed['name'],
      )));
    }
    else {
      $err = TRUE;
    }
  }
  else {
    $err = TRUE;
  }
  if ($err) {
    drupal_set_message(t('Feed cannot be processed!'), 'error');
  }
  drupal_goto(FEED_IMPORT_PATH);
}

/**
 * Export feed
 *
 * @param int $id
 *   Feed id to be exported
 *
 * @return string
 *   A textarea containing serialized feed code
 */
function feed_import_export_feed_page($id = 0) {
  if ($id == 'all') {
    $feed = FeedImport::loadFeeds(FALSE);
  }
  else {
    $feed = FeedImport::loadFeeds(FALSE, $id);
    if (!$feed) {
      return MENU_NOT_FOUND;
    }
  }

  // Set page title.
  drupal_set_title(t('Copy and save all code below.'));

  // A base 64 encoded string is much easy to save than a serialized string.
  $feed = base64_encode(serialize($feed));
  return array(
    '#theme' => 'textarea',
    '#value' => $feed,
    '#rows' => 20,
  );
}

/**
 * Feed import form
 */
function feed_import_import_feed_form($form, &$form_state) {
  $form['code'] = array(
    '#type' => 'textarea',
    '#title' => t('Paste your code here'),
    '#rows' => 5,
  );
  $form['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Start import'),
  );
  return $form;
}

/**
 * Feed import form validate
 */
function feed_import_import_feed_form_validate($form, &$form_state) {

  // This is to silence unserialize warning.
  $ok = @unserialize(base64_decode(trim($form_state['values']['code'])));
  if (!$ok) {
    form_error($form['code'], t("Code couldn't be imported!"));
  }
}

/**
 * Feed import form submit
 */
function feed_import_import_feed_form_submit($form, &$form_state) {

  // Get all names and machine names.
  $feeds = FeedImport::loadFeeds();
  $names = array(
    'name' => array(),
    'machine_name' => array(),
  );
  if (!empty($feeds)) {
    foreach ($feeds as $feed) {
      $names['machine_name'][$feed['machine_name']] = TRUE;
      $names['name'][$feed['name']] = TRUE;
      $feed = NULL;
    }
  }

  // Silence unserialize warning.
  $feeds = @unserialize(base64_decode(trim($form_state['values']['code'])));

  // There is only one feed.
  if (isset($feeds['xpath']['#root'])) {
    $feeds['enabled'] = 0;

    // Mark feed name as imported if name already exists.
    if (isset($names['name'][$feeds['name']])) {
      $feeds['name'] .= ' (' . t('imported') . ')';
    }

    // Machine names must be unique.
    $index = '';
    while (isset($names['machine_name'][$feeds['machine_name'] . $index])) {
      $index = (int) $index + 1;
    }
    $feeds['machine_name'] .= $index;
    FeedImport::saveFeed($feeds);
    drupal_set_message(t('Feed @name was imported and disabled.', array(
      '@name' => $feeds['name'],
    )));
  }
  else {
    $imported = array();
    foreach ($feeds as &$feed) {
      $feed['enabled'] = 0;

      // Mark feed name as imported if name already exists.
      if (isset($names['name'][$feed['name']])) {
        $feed['name'] .= ' (' . t('imported') . ')';
      }

      // Machine name must be unique.
      $index = '';
      while (isset($names['machine_name'][$feed['machine_name'] . $index])) {
        $index = (int) $index + 1;
      }
      $feed['machine_name'] .= $index;

      // Add to all amchine names.
      $names['machine_name'][$feed['machine_name']] = TRUE;
      FeedImport::saveFeed($feed);
      $imported[] = $feed['name'];
      $feed = NULL;
    }
    $imported = implode(', ', $imported);
    drupal_set_message(t('Feeds @names were imported and disabled.', array(
      '@names' => $imported,
    )));
  }
  drupal_goto(FEED_IMPORT_PATH);
}

/**
 * Add a new feed form
 */
function feed_import_add_new_feed_form($form, &$form_state) {
  $form['name'] = array(
    '#type' => 'textfield',
    '#title' => t('Feed name'),
    '#description' => t('This usually is source name.'),
    '#required' => TRUE,
  );
  $form['machine_name'] = array(
    '#type' => 'machine_name',
    '#title' => t('Feed machine name'),
    '#description' => t('This must be unique for each feed and must be not numeric.') . '<br />' . t('Once saved this can not be changed!'),
    '#required' => TRUE,
    '#machine_name' => array(
      'source' => array(
        'name',
      ),
      'exists' => 'feed_import_machine_name_exists',
    ),
  );
  $entities = FeedImport::getEntityInfo();
  $options = array();
  foreach ($entities as &$entity) {
    $options[$entity['name']] = $entity['name'];
  }
  $form['entity'] = array(
    '#type' => 'select',
    '#options' => $options,
    '#default_value' => 'node',
    '#title' => t('Entity name'),
    '#description' => t('Entity where you want to import content. Ex: node, user, ...'),
    '#required' => TRUE,
  );
  $form['url'] = array(
    '#type' => 'textfield',
    '#title' => t('URL to feed'),
    '#description' => t('Please use a valid url that returns valid content!'),
    '#required' => TRUE,
  );
  $options = array();
  $functions = array_keys(FeedImport::processFunctions());
  foreach ($functions as $f) {
    $options[$f] = $f;
  }
  $form['process_function'] = array(
    '#type' => 'select',
    '#options' => $options,
    '#title' => t('Select feed processing function'),
    '#description' => t('Read help for more information about processing functions'),
  );
  $form['time'] = array(
    '#type' => 'textfield',
    '#title' => t('Keep imported items (seconds)'),
    '#description' => t('This is used to delete items after expiration.'),
    '#default_value' => 0,
    '#required' => TRUE,
  );
  $form['enabled'] = array(
    '#type' => 'checkbox',
    '#title' => t('Import at cron'),
    '#default_value' => 0,
    '#description' => t('Check this if you want to import feed items when cron runs.'),
  );
  $form['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Add feed'),
  );
  return $form;
}

/**
 * Add new feed form validate
 */
function feed_import_add_new_feed_form_validate($form, &$form_state) {
  $feeds = FeedImport::loadFeeds();
  if (is_numeric($form_state['values']['machine_name']) || is_numeric($form_state['values']['machine_name'][0])) {
    form_error($form['machine_name'], t('Machine name must be not numeric!'));
  }
  if (isset($feeds[$form_state['values']['machine_name']])) {
    form_error($form['machine_name'], t('Feed machine name already exists!'));
  }
}

/**
 * Add new feed form submit
 */
function feed_import_add_new_feed_form_submit($form, &$form_state) {
  $entity = FeedImport::getEntityInfo($form_state['values']['entity']);
  $feed = array(
    'name' => $form_state['values']['name'],
    'machine_name' => $form_state['values']['machine_name'],
    'url' => $form_state['values']['url'],
    'time' => (int) $form_state['values']['time'],
    'enabled' => (int) $form_state['values']['enabled'],
    'entity_info' => array(
      '#entity' => $form_state['values']['entity'],
      '#table_pk' => $entity['column'],
    ),
    'xpath' => array(
      '#skip_imported_items' => FALSE,
      '#root' => '',
      '#uniq' => '',
      '#process_function' => $form_state['values']['process_function'],
      '#settings' => array(),
      '#items' => array(),
    ),
  );

  // Save feed.
  FeedImport::saveFeed($feed);
  drupal_set_message(t('Feed @name was created', array(
    '@name' => $form_state['values']['name'],
  )));
  drupal_goto(FEED_IMPORT_PATH);
}

/**
 * Checks if a machine name already exists.
 * Callback for machine_name input type.
 *
 * @param string $name
 *   Name to check
 *
 * @return bool
 *   If machine name exists or not
 */
function feed_import_machine_name_exists($name) {
  $feeds = FeedImport::loadFeeds();
  foreach ($feeds as &$feed) {
    if ($feed['machine_name'] == $name) {
      return TRUE;
    }
  }
  return FALSE;
}

/**
 * Enable/disable feeds
 *
 * @param string $how
 *   enable/disable options
 * @param int $id
 *   Feed id to enable or disable
 */
function feed_import_change_status($how, $id = 0) {
  $id = (int) $id;
  if ($id) {
    $how = $how == 'enable' ? 1 : 0;
    db_update('feed_import_settings')
      ->fields(array(
      'enabled' => $how,
    ))
      ->condition('id', $id, '=')
      ->execute();
    drupal_set_message($how ? t('Feed enabled') : t('Feed disabled'));
  }
  drupal_goto(FEED_IMPORT_PATH);
}

/**
 * Feed delete form
 */
function feed_import_delete_feed_form($form, &$form_state, $id = 0) {
  $feed = FeedImport::loadFeeds(FALSE, $id);
  if (!$feed) {
    return MENU_NOT_FOUND;
  }
  $form['machine_name'] = array(
    '#type' => 'value',
    '#value' => $feed['machine_name'],
  );
  $form['name'] = array(
    '#type' => 'value',
    '#value' => $feed['name'],
  );
  return confirm_form($form, t('This action cannot be undone!'), FEED_IMPORT_PATH);
}

/**
 * Feed delete form submit
 */
function feed_import_delete_feed_form_submit($form, &$form_state) {
  $name = $form_state['values']['name'];
  $mname = $form_state['values']['machine_name'];
  db_delete('feed_import_settings')
    ->condition('machine_name', $mname, '=')
    ->execute();
  db_delete('feed_import_hashes')
    ->condition('feed_machine_name', $mname, '=')
    ->execute();
  drupal_set_message(t('Feed @name deleted!', array(
    '@name' => $name,
  )));
  drupal_goto(FEED_IMPORT_PATH);
}

/**
 * Edit feed form
 */
function feed_import_edit_feed_form($form, &$form_state, $id) {

  // Get feed.
  $id = (int) $id;
  $feed = FeedImport::loadFeeds(FALSE, $id);
  if (!$feed) {
    drupal_set_message(t("Feed doesn't exist!"), 'error');
    drupal_goto(FEED_IMPORT_PATH);
    return;
  }

  // Set page title.
  drupal_set_title(t('Edit feed @name', array(
    '@name' => $feed['name'],
  )), PASS_THROUGH);

  // Basic feed fields.
  $form['id'] = array(
    '#type' => 'value',
    '#value' => $feed['id'],
  );
  $form['name'] = array(
    '#type' => 'textfield',
    '#title' => t('Feed name'),
    '#description' => t('This usually is source name.'),
    '#default_value' => $feed['name'],
    '#required' => TRUE,
  );
  $options = array();
  $entities = FeedImport::getEntityInfo();
  foreach ($entities as &$entity) {
    $options[$entity['name']] = $entity['name'];
  }
  if (!isset($feed['entity_info']['#entity']) || empty($feed['entity_info']['#entity'])) {
    $feed['entity_info']['#entity'] = 'node';
  }
  $form['entity'] = array(
    '#type' => 'select',
    '#options' => $options,
    '#default_value' => isset($feed['entity_info']['#entity']) ? $feed['entity_info']['#entity'] : 'node',
    '#title' => t('Entity name'),
    '#description' => t('Entity where you want to import content. Ex: "node"'),
    '#required' => TRUE,
    '#disabled' => is_array($feed['xpath']['#items']) ? count($feed['xpath']['#items']) : 0,
  );
  $form['url'] = array(
    '#type' => 'textfield',
    '#title' => t('URL to feed'),
    '#description' => t('Please use a valid url that returns valid content!'),
    '#default_value' => $feed['url'],
    '#required' => TRUE,
  );
  $options = array();
  $functions = FeedImport::processFunctions();
  foreach ($functions as $f => &$func) {
    $options[$f] = $f;
  }
  $form['process_function'] = array(
    '#type' => 'select',
    '#options' => $options,
    '#default_value' => isset($feed['xpath']['#process_function']) ? $feed['xpath']['#process_function'] : reset($options),
    '#title' => t('Select process function'),
    '#ajax' => array(
      'event' => 'change',
      'callback' => 'feed_import_ajax_change_process_function_settings',
      'wrapper' => 'process_function_settings',
      'method' => 'replace',
    ),
  );

  // Process function settings.
  $form['settings'] = array(
    '#type' => 'fieldset',
    '#collapsible' => TRUE,
    '#collapsed' => TRUE,
    '#title' => t('Process function settings'),
    '#tree' => TRUE,
    '#attributes' => array(
      'id' => 'process_function_settings',
    ),
  );
  if (isset($form_state['values']['process_function'])) {
    $process_func = $form_state['values']['process_function'];
    $form['settings']['#collapsed'] = FALSE;
  }
  else {
    $process_func = $form['process_function']['#default_value'];
  }

  // Get current process function info.
  $functions = $functions[$process_func];

  // Add settings info.
  if (!empty($functions['settings'])) {
    $form['settings']['info'] = array(
      '#markup' => $functions['info'] . '<br /><u>' . t('If a field value is not valid default value will be used without any warnings!') . '</u>',
    );
    $use_default = $process_func == $feed['xpath']['#process_function'];
    foreach ($functions['settings'] as $field_name => &$field_info) {
      if ($use_default && isset($feed['xpath']['#settings'][$field_name])) {
        $field_info['default'] = $feed['xpath']['#settings'][$field_name];
      }
      $form['settings'][$field_name] = array(
        '#type' => 'textfield',
        '#title' => check_plain($field_info['title']),
        '#description' => check_plain($field_info['description']),
        '#default_value' => $field_info['default'],
      );
      $field_info = NULL;
    }
  }
  else {
    $form['settings']['info'] = array(
      '#markup' => t("This process function doesn't have settings."),
    );
  }
  unset($functions);
  $form['time'] = array(
    '#type' => 'textfield',
    '#title' => t('Keep imported items (seconds)'),
    '#description' => t("This is used to delete items after expiration. 0 means item don't expire."),
    '#default_value' => $feed['time'],
    '#required' => TRUE,
  );
  $form['enabled'] = array(
    '#type' => 'checkbox',
    '#title' => t('Import at cron'),
    '#default_value' => $feed['enabled'],
    '#description' => t('Check this if you want to import feed items when cron runs.'),
  );

  // XPATH settings fieldset.
  $form['xpath'] = array(
    '#type' => 'fieldset',
    '#collapsible' => TRUE,
    '#collapsed' => FALSE,
    '#title' => t('XPATH settings'),
  );
  $form['xpath']['root'] = array(
    '#type' => 'textfield',
    '#default_value' => isset($feed['xpath']['#root']) ? $feed['xpath']['#root'] : '',
    '#title' => t('Enter parent item xpath'),
    '#description' => t("Usualy this starts with // and it's base query (context for all items)."),
  );
  $form['xpath']['uniq'] = array(
    '#type' => 'textfield',
    '#default_value' => isset($feed['xpath']['#uniq']) ? $feed['xpath']['#uniq'] : '',
    '#title' => t('Enter xpath to a unique identifier of item'),
    '#description' => t("This is unique per item. Usually it's an ID. If empty then items will not be monitorized."),
  );
  $form['xpath']['skip_imported_items'] = array(
    '#type' => 'checkbox',
    '#title' => t('Skip already imported items'),
    '#default_value' => !empty($feed['xpath']['#skip_imported_items']),
    '#description' => t('This is possible only when items are monitorized.'),
  );

  // XPATH items.
  $form['xpath']['items'] = array(
    '#type' => 'container',
    '#attributes' => array(
      'id' => 'xpath_items',
    ),
    '#prefix' => '<label for="xpath_items">' . t('Current fields') . '</label>',
  );
  $xpaths = array();
  if (isset($form_state['#current_item'])) {
    for ($i = 0; $i <= $form_state['#current_item']; $i++) {
      if (isset($form_state['values']['xpath_' . $i])) {
        $default_val = isset($form_state['values']['xpath_' . $i]) ? $form_state['values']['xpath_' . $i] : '';
        if (is_array($default_val)) {
          $default_val = implode(PHP_EOL, $default_val);
        }
        $vars = array(
          '#field' => $form_state['complete form']['xpath']['items']['container_' . $i]['#title'],
          '#xpath' => $default_val,
          '#default_value' => isset($form_state['values']['default_' . $i]) ? $form_state['values']['default_' . $i] : '',
          '#default_action' => isset($form_state['values']['default_action_' . $i]) ? $form_state['values']['default_action_' . $i] : '',
        );
        $xpaths += feed_import_generate_xpath_item($i, $vars);
      }
    }
  }
  else {
    $form_state['#current_item'] = -1;
    if (!empty($feed['xpath']['#items'])) {
      foreach ($feed['xpath']['#items'] as &$field) {
        $form_state['#current_item']++;
        $xpaths += feed_import_generate_xpath_item($form_state['#current_item'], $field, TRUE);
      }
    }
  }
  $cbk = isset($form_state['triggering_element']['#parents'][0]) ? $form_state['triggering_element']['#parents'][0] : '';
  switch (TRUE) {
    case $cbk == 'add_new_item':
      $form_state['#field_added'] = FALSE;
      $vars = $form_state['values']['add_new_item_mode'] ? 'add_new_item_field' : 'add_new_item_manual';
      $vars = drupal_strtolower($form_state['values'][$vars]);
      if (empty($vars)) {
        break;
      }
      else {
        $i = 0;
        while (isset($form_state['complete form']['xpath']['items']['container_' . $i])) {
          if ($form_state['complete form']['xpath']['items']['container_' . $i]['#title'] == $vars) {
            break 2;
          }
          $i++;
        }
        unset($i);
      }
      $form_state['#field_added'] = TRUE;
      $form_state['#current_item']++;
      $xpaths += feed_import_generate_xpath_item($form_state['#current_item'], array(
        '#field' => $vars,
      ));
      break;
    case preg_match('/remove_container_([0-9]{1,9})/', $cbk, $match):

      // Delete container.
      unset($xpaths['container_' . $match[1]]);
      break;
  }
  $form['xpath']['items'] += $xpaths;
  unset($xpaths);

  // Add new name.
  $fields_options = array();
  foreach ($entities[$feed['entity_info']['#entity']]['columns'] as $f => &$v) {
    if (!isset($feed['xpath']['#items'][$f])) {
      $fields_options[$f] = $f;
    }
  }
  $form['xpath']['add_new_item_mode'] = array(
    '#type' => 'checkbox',
    '#title' => t('Use defined fields'),
    '#default_value' => TRUE,
  );
  $form['xpath']['add_new_item_field'] = array(
    '#type' => 'select',
    '#options' => $fields_options,
    '#title' => t('Select defined field'),
    '#description' => t('Select field name and click "' . t('Add field') . '" button'),
    '#states' => array(
      'visible' => array(
        ':input[name=add_new_item_mode]' => array(
          'checked' => TRUE,
        ),
      ),
    ),
  );
  $form['xpath']['add_new_item_manual'] = array(
    '#type' => 'textfield',
    '#title' => t('Enter field name'),
    '#description' => t('Write field name and click "' . t('Add field') . '" button'),
    '#states' => array(
      'visible' => array(
        ':input[name=add_new_item_mode]' => array(
          'checked' => FALSE,
        ),
      ),
    ),
  );

  // Add new item button.
  $form['xpath']['add_new_item'] = array(
    '#type' => 'button',
    '#value' => t('Add field'),
    '#inline' => TRUE,
    '#ajax' => array(
      'event' => 'click',
      'callback' => 'feed_import_ajax_add_new_item',
      'wrapper' => 'xpath_items',
      'method' => 'append',
    ),
  );

  // Submit buttons.
  $form['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Save feed'),
  );
  $form['cancel'] = array(
    '#type' => 'submit',
    '#value' => t('Cancel'),
  );

  // Add js.
  $form['#attached'] = array(
    'js' => array(
      drupal_get_path('module', 'feed_import') . '/feed_import.js' => array(
        'type' => 'file',
      ),
    ),
  );
  return $form;
}

/**
 * Edit feed form validate
 */
function feed_import_edit_feed_form_validate($form, &$form_state) {

  // Cancel button pressed.
  $but = isset($form_state['clicked_button']['#parents'][0]) ? $form_state['clicked_button']['#parents'][0] : '';
  if ($but == 'cancel') {
    drupal_goto(FEED_IMPORT_PATH);
  }

  // Check settings for procss function.
  // These does not show errors.
  if (!empty($form_state['values']['settings'])) {
    $func = FeedImport::processFunctions();
    $func = $func[$form_state['values']['process_function']];

    // Prevent fatal errors by checking if function exists.
    if (is_array($func['validate'])) {
      if (!method_exists($func['validate'][0], $func['validate'][1])) {
        return;
      }
    }
    else {
      if (empty($func['validate']) || !function_exists($func['validate'])) {
        return;
      }
    }
    foreach ($form_state['values']['settings'] as $field => &$value) {
      if (isset($func['settings'][$field]['default'])) {
        $default = $func['settings'][$field]['default'];
      }
      else {
        $default = NULL;
      }
      $value = call_user_func($func['validate'], $field, $value, $default);
    }
  }
  else {
    $form_state['values']['settings'] = array();
  }
}

/**
 * Edit feed form submit
 */
function feed_import_edit_feed_form_submit($form, &$form_state) {
  $values =& $form_state['values'];
  $feed = FeedImport::loadFeeds(FALSE, $values['id']);
  $entity = FeedImport::getEntityInfo($values['entity']);
  $items = array();
  for ($i = 0; $i <= $form_state['#current_item']; $i++) {
    if (isset($form_state['complete form']['xpath']['items']['container_' . $i]['#title'])) {
      $field = $form_state['complete form']['xpath']['items']['container_' . $i]['#title'];
    }
    else {
      continue;
    }
    if ($field) {
      $items[$field] = array(
        '#field' => $field,
        '#column' => array_key_exists($field, $entity['columns']) ? $entity['columns'][$field] : $field,
        '#xpath' => explode(PHP_EOL, $values['xpath_' . $i]),
        '#default_value' => $values['default_' . $i],
        '#default_action' => $values['default_action_' . $i],
        '#filter' => isset($feed['xpath']['#items'][$field]['#filter']) ? $feed['xpath']['#items'][$field]['#filter'] : array(),
        '#pre_filter' => isset($feed['xpath']['#items'][$field]['#pre_filter']) ? $feed['xpath']['#items'][$field]['#pre_filter'] : array(),
      );
    }
  }
  $feed = array(
    'id' => (int) $values['id'],
    'name' => $values['name'],
    'machine_name' => $feed['machine_name'],
    'url' => $values['url'],
    'time' => (int) $values['time'],
    'enabled' => (int) $values['enabled'],
    'entity_info' => array(
      '#entity' => $values['entity'],
      '#table_pk' => $entity['column'],
    ),
    'xpath' => array(
      '#skip_imported_items' => (bool) $values['skip_imported_items'],
      '#root' => $values['root'],
      '#uniq' => $values['uniq'],
      '#process_function' => $values['process_function'],
      '#items' => $items,
      '#settings' => $values['settings'],
    ),
  );

  // Save feed.
  FeedImport::saveFeed($feed, TRUE);
  drupal_set_message(t('Feed @name saved!', array(
    '@name' => $feed['name'],
  )));
}

/**
 * Ajax callback to add a new item
 */
function feed_import_ajax_add_new_item($form, &$form_state) {
  if ($form_state['#field_added']) {
    return $form['xpath']['items']['container_' . $form_state['#current_item']];
  }
  else {
    return NULL;
  }
}

/**
 * Ajax callback to remove an item
 */
function feed_import_ajax_remove_item($form, &$form_state) {

  // Send empty string to remove container.
  return '';
}

/**
 * Ajax callback to change settings for process function
 */
function feed_import_ajax_change_process_function_settings($form, &$form_state) {
  return $form['settings'];
}

/**
 * Generate field
 *
 * @param int $pos
 *   Fieldset number
 * @param array $values
 *   Array containing default values
 * @param bool $collapsed
 *   Inicates if fieldset is collapsed
 *
 * @return array
 *   Fieldset containing xpath inputs
 */
function feed_import_generate_xpath_item($pos = 0, $values = NULL, $collapsed = FALSE) {
  $container = 'container_' . $pos;
  $item[$container] = array(
    '#type' => 'fieldset',
    '#collapsible' => TRUE,
    '#collapsed' => $collapsed,
    '#title' => isset($values['#field']) ? $values['#field'] : t('Unspecified field'),
    '#attributes' => array(
      'id' => 'item_container_' . $pos,
    ),
  );
  $container =& $item[$container];
  $values['#xpath'] = isset($values['#xpath']) ? $values['#xpath'] : '';
  if (is_array($values['#xpath'])) {
    $values['#xpath'] = implode(PHP_EOL, $values['#xpath']);
  }
  $container['xpath_' . $pos] = array(
    '#type' => 'textarea',
    '#default_value' => $values['#xpath'],
    '#title' => t('XPATH'),
    '#description' => t('Enter XPATH to item. You can enter multiple XPATHs (one per line) until one passes pre-filter.'),
  );
  $container['default_action_' . $pos] = array(
    '#type' => 'select',
    '#options' => FeedImport::getDefaultActions(),
    '#default_value' => isset($values['#default_action']) ? $values['#default_action'] : 'default_value',
    '#title' => t('Action when filtered result is empty'),
    '#description' => t('If the filter is empty you can choose what action to take next.'),
  );
  $container['default_' . $pos] = array(
    '#type' => 'textfield',
    '#default_value' => isset($values['#default_value']) ? $values['#default_value'] : '',
    '#title' => t('Default value'),
    '#description' => t('If no XPATH passes pre-filter then use a default value.'),
    '#prefix' => '<div style="display: none;" rel="default_action_' . $pos . '">',
    '#suffix' => '</div>',
  );
  $container['remove_container_' . $pos] = array(
    '#type' => 'button',
    '#value' => t('Remove field @field', array(
      '@field' => drupal_strtoupper($container['#title']),
    )),
    '#ajax' => array(
      'event' => 'click',
      'wrapper' => 'item_container_' . $pos,
      'method' => 'replace',
      'callback' => 'feed_import_ajax_remove_item',
    ),
  );
  return $item;
}

/**
 * Edit filter form
 */
function feed_import_edit_filter_form($form, &$form_state, $f = '#filter', $id = 0) {
  $feed = FeedImport::loadFeeds(FALSE, $id);
  if (!$feed) {
    drupal_set_message(t("Feed doesn't exist!"), 'error');
    return;
  }
  if (!isset($form_state['#item_filter'])) {

    // Save filter.
    $form_state['#item_filter'] = $f;

    // Save fields to be filtered.
    $form_state['#filter_fields'] = array_keys($feed['xpath']['#items']);
  }
  $form['#filter_fields'] = $form_state['#filter_fields'];
  $param_field = variable_get('feed_import_field_param_name', '[field]');
  $help = array();
  $help[0] = t('Filter name') . ': ' . t('A name given by you for this filter.');
  $help[1] = t('Filter function') . ': ' . t('Name of the php function to apply on field value.') . ' ';
  $help[1] .= t('You may also use a static function like this: ClassName::functionName.') . ' ';
  $help[1] .= t('Also check our provided filters in FeedImportFilter class.');
  $help[2] = t('Function params') . ': ' . t('Enter here params (one per line) for php function.') . ' ';
  $help[2] .= t('Enter "@param_field" (without quotes) were you want to be sent field value as parameter.', array(
    '@param_field' => $param_field,
  )) . ' ';
  $help[3] = t('Filtered value is the resulted string of all function calls from top to bottom.');
  $help = implode('<br />', $help);
  $form['help'] = array(
    '#markup' => $help,
  );
  $vars = array(
    '@name' => $feed['name'],
    '@filter' => $form_state['#item_filter'] == '#filter' ? t('filters') : t('pre-filters'),
  );

  // Set page title.
  drupal_set_title(t('Edit @filter for @name', $vars), PASS_THROUGH);
  $form['id'] = array(
    '#type' => 'value',
    '#value' => $feed['id'],
  );
  if (isset($form_state['values'])) {
    foreach ($form_state['#filter_fields'] as &$field) {
      $filters = array();
      $filter[$field]['#tree'] = TRUE;
      $pos = 0;
      if (!empty($form_state['values']['table_content'][$field])) {
        foreach ($form_state['values']['table_content'][$field] as &$filter) {
          $vars = array(
            '#name' => $filter['name'],
            '#function' => $filter['function'],
            '#params' => explode(PHP_EOL, $filter['params']),
          );
          $filters[$field][$pos] = feed_import_new_filter($pos, $vars);
          $pos++;
        }
      }

      // Add new field.
      if ($form_state['#add_filter'] == $field) {
        $vars = array(
          '#name' => '',
          '#function' => '',
          '#params' => array(
            $param_field,
          ),
        );
        $filters[$field][$pos] = feed_import_new_filter($pos, $vars);
      }
      $form['container_' . $field] = array(
        '#type' => 'fieldset',
        '#title' => check_plain($field),
        '#collapsible' => TRUE,
        '#collapsed' => $field != $form_state['#add_filter'] && $field != $form_state['#delete_filter'],
        'table_content' => array(
          '#tree' => TRUE,
        ) + $filters,
        'actions' => feed_import_add_filter_actions($field),
      );
    }
  }
  else {
    foreach ($feed['xpath']['#items'] as $field => &$val) {
      $filters = array();
      $filters[$field]['#tree'] = TRUE;
      $pos = 0;
      foreach ($val[$form_state['#item_filter']] as $name => &$filter) {
        $vars = array(
          '#name' => $name,
          '#function' => $filter['#function'],
          '#params' => $filter['#params'],
        );
        $filters[$field][$pos] = feed_import_new_filter($pos, $vars);
        $pos++;
      }
      $form['container_' . $field] = array(
        '#type' => 'fieldset',
        '#title' => check_plain($field),
        '#collapsible' => TRUE,
        '#collapsed' => TRUE,
        'table_content' => array(
          '#tree' => TRUE,
        ) + $filters,
        'actions' => feed_import_add_filter_actions($field),
      );
    }
  }
  $form['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Save filters'),
    '#prefix' => t('Your filters will be saved only after you press the button below.') . '<br />',
  );
  return $form;
}

/**
 * Edit filter form validate
 */
function feed_import_edit_filter_form_validate(&$form, &$form_state) {
  if ($form_state['submitted']) {
    return;
  }
  $delete_filter = NULL;
  $add_filter = NULL;
  foreach ($form_state['#filter_fields'] as &$field) {
    $cbk = $form_state['clicked_button']['#array_parents'];
    switch (TRUE) {
      case $cbk[1] == 'actions' && preg_match('/add_new_filter_([a-zA-Z0-9_]{1,255})/', $cbk[2], $match):
        $add_filter = $match[1];
        break;
      case $cbk[1] == 'actions' && preg_match('/delete_selected_filters_([a-zA-Z0-9_]{1,255})/', $cbk[2], $match):
        $delete_filter = $match[1];
        break;
    }

    // Delete selected.
    if ($field == $delete_filter) {
      foreach ($form_state['values']['table_content'][$field] as $key => &$filter) {
        if ($filter['selected']) {
          unset($form_state['values']['table_content'][$field][$key]);
        }
      }
    }
    if (!empty($form_state['values']['table_content'][$field])) {

      // Set filters order.
      usort($form_state['values']['table_content'][$field], 'feed_import_sort_filter_by_weight');
    }
  }
  $form_state['#add_filter'] = $add_filter;
  $form_state['#delete_filter'] = $delete_filter;
}

/**
 * Edit filter form submit
 */
function feed_import_edit_filter_form_submit($form, &$form_state) {
  $values =& $form_state['values'];
  $feed = FeedImport::loadFeeds(FALSE, $values['id']);
  foreach ($feed['xpath']['#items'] as $field => &$item) {
    $item[$form_state['#item_filter']] = array();
    if (!empty($values['table_content'][$field])) {
      usort($values['table_content'][$field], 'feed_import_sort_filter_by_weight');
      foreach ($values['table_content'][$field] as &$filter) {
        if (!$filter['name'] || !$filter['function']) {
          continue;
        }
        if (!$filter['params']) {
          $filter['params'] = array(
            variable_get('feed_import_field_param_name', '[field]'),
          );
        }
        else {
          $filter['params'] = explode(PHP_EOL, $filter['params']);
          $filter['params'] = array_map('trim', $filter['params']);
        }
        $item[$form_state['#item_filter']][$filter['name']] = array(
          '#function' => trim($filter['function']),
          '#params' => $filter['params'],
        );
        $filter = NULL;
      }
    }
  }

  // Save feed.
  FeedImport::saveFeed($feed, TRUE);
  $vars = array(
    '@filter' => $form_state['#item_filter'] == '#filter' ? t('Filters') : t('Pre-filters'),
    '@name' => $feed['name'],
  );
  drupal_set_message(t('@filter saved for @name', $vars));
}

/**
 * usort() callback, for sorting filters by weight
 */
function feed_import_sort_filter_by_weight($a, $b) {
  if ($a['weight'] == $b['weight']) {
    return 0;
  }
  return $a['weight'] > $b['weight'] ? 1 : -1;
}

/**
 * Return new filter elements
 *
 * @param int $pos
 *   Filter position
 * @param array $values
 *   Default filter values
 *
 *  @return array
 *    Array containing filter html forms
 */
function feed_import_new_filter($pos = 0, $values = NULL) {
  $values['#params'] = isset($values['#params']) ? $values['#params'] : '';
  if (is_array($values['#params'])) {
    $values['#params'] = implode(PHP_EOL, $values['#params']);
  }
  return array(
    'name' => array(
      '#type' => 'textfield',
      '#size' => 30,
      '#default_value' => isset($values['#name']) ? $values['#name'] : '',
    ),
    'function' => array(
      '#type' => 'textfield',
      '#size' => 30,
      '#default_value' => isset($values['#function']) ? $values['#function'] : '',
    ),
    'params' => array(
      '#type' => 'textarea',
      '#default_value' => $values['#params'],
      '#rows' => 2,
    ),
    'selected' => array(
      '#type' => 'checkbox',
      '#default_value' => 0,
    ),
    'weight' => array(
      '#type' => 'weight',
      '#delta' => 15,
      '#default_value' => $pos,
      '#attributes' => array(
        'class' => array(
          'weight',
        ),
      ),
    ),
  );
}

/**
 * Add filter button actions: add new, remove all
 *
 * @param string $field
 *   Field name
 *
 * @return array
 *   Array with buttons
 */
function feed_import_add_filter_actions($field) {
  return array(
    'add_new_filter_' . $field => array(
      '#type' => 'button',
      '#value' => t('Add new filter to @field', array(
        '@field' => drupal_strtoupper($field),
      )),
    ),
    'delete_selected_filters_' . $field => array(
      '#type' => 'button',
      '#value' => t('Remove selected from @field', array(
        '@field' => drupal_strtoupper($field),
      )),
    ),
  );
}

/**
 * Implements hook_theme().
 */
function feed_import_theme() {
  return array(
    'feed_import_edit_filter_form' => array(
      'render element' => 'form',
    ),
    'feed_import_reorder_fields_form' => array(
      'render element' => 'form',
    ),
  );
}

/**
 * Theme form feed_import_edit_filter_form
 */
function theme_feed_import_edit_filter_form($form) {
  $form = $form['form'];
  $header = array(
    t('Filter name'),
    t('Filter function'),
    t('Function params (one per line)'),
    t('Select'),
    t('Weight'),
  );
  foreach ($form['#filter_fields'] as &$field) {
    $rows = array();
    if (!empty($form['container_' . $field]['table_content'][$field])) {
      foreach ($form['container_' . $field]['table_content'][$field] as $id => &$row) {
        if (!is_numeric($id)) {
          continue;
        }

        // Table columns.
        $data = array(
          'name',
          'function',
          'params',
          'selected',
          'weight',
        );
        foreach ($data as &$d) {
          if (isset($row[$d]['#checked'])) {
            $row[$d]['#checked'] = FALSE;
          }
          $row[$d]['#value'] = $row[$d]['#default_value'];
          $d = drupal_render($row[$d]);
        }
        $rows[] = array(
          'data' => $data,
          'class' => array(
            'draggable',
          ),
        );
      }
    }
    drupal_add_tabledrag('table_' . $field, 'order', 'sibling', 'weight');
    $form['container_' . $field]['table_content'][$field] = array(
      '#theme' => 'table',
      '#header' => $header,
      '#rows' => $rows,
      '#attributes' => array(
        'id' => 'table_' . $field,
      ),
      '#empty' => t('There are no filters.'),
    );
  }
  return drupal_render_children($form);
}

/**
 * Re-order fields form.
 */
function feed_import_reorder_fields_form($form, &$form_state, $id = 0) {
  $feed = FeedImport::loadFeeds(FALSE, $id);
  if (!$feed) {
    drupal_set_message(t("Feed doesn't exist!"), 'error');
    return;
  }
  drupal_set_title(t('Reorder fields for @name', array(
    '@name' => $feed['name'],
  )), PASS_THROUGH);
  $form_state['#feed'] = $feed;
  $fields = empty($feed['xpath']['#items']) ? array() : array_keys($feed['xpath']['#items']);
  $form['table_content'] = array(
    '#tree' => TRUE,
  );
  $form['#feed_fields'] = $fields;
  $i = 0;
  $delta = count($fields);
  foreach ($fields as &$field) {
    $form['table_content'][$field] = array(
      'field' => array(
        '#markup' => $field,
      ),
      'weight' => array(
        '#type' => 'weight',
        '#delta' => $delta,
        '#default_value' => $i,
        '#attributes' => array(
          'class' => array(
            'weight',
          ),
        ),
      ),
    );
    $i++;
  }
  $form['table'] = NULL;
  $form['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Save order'),
  );
  return $form;
}

/**
 * Re-order fields form submit.
 */
function feed_import_reorder_fields_form_submit($form, &$form_state) {
  if (empty($form_state['values']['table_content'])) {
    return;
  }
  $fields = $form_state['values']['table_content'];
  uasort($fields, 'feed_import_sort_filter_by_weight');
  foreach ($fields as &$value) {
    $value = NULL;
  }
  $feed =& $form_state['#feed'];
  $feed['xpath']['#items'] = array_merge($fields, $feed['xpath']['#items']);
  FeedImport::saveFeed($feed, TRUE);
  drupal_set_message(t('New order saved!'));
}

/**
 * Theme form feed_import_edit_filter_form
 */
function theme_feed_import_reorder_fields_form($form) {
  $form = $form['form'];
  if (empty($form['#feed_fields'])) {
    $form['#feed_fields'] = array();
  }
  else {
    foreach ($form['#feed_fields'] as &$field) {
      $field = array(
        drupal_render($form['table_content'][$field]['field']),
        drupal_render($form['table_content'][$field]['weight']),
      );
      $field = array(
        'data' => $field,
        'class' => array(
          'draggable',
        ),
      );
    }
  }
  $form['table'] = array(
    '#theme' => 'table',
    '#header' => array_map('t', array(
      'Field',
      'Weight',
    )),
    '#rows' => $form['#feed_fields'],
    '#attributes' => array(
      'id' => 'table',
    ),
    '#empty' => t('There are no fields.'),
  );
  drupal_add_tabledrag('table', 'order', 'sibling', 'weight');
  return drupal_render_children($form);
}

Functions

Namesort descending Description
feed_import_add_filter_actions Add filter button actions: add new, remove all
feed_import_add_new_feed_form Add a new feed form
feed_import_add_new_feed_form_submit Add new feed form submit
feed_import_add_new_feed_form_validate Add new feed form validate
feed_import_add_to_delete Schedule an item for removing from feed import items at the end of the script
feed_import_ajax_add_new_item Ajax callback to add a new item
feed_import_ajax_change_process_function_settings Ajax callback to change settings for process function
feed_import_ajax_remove_item Ajax callback to remove an item
feed_import_change_status Enable/disable feeds
feed_import_cron Implements hook_cron().
feed_import_delete_feed_form Feed delete form
feed_import_delete_feed_form_submit Feed delete form submit
feed_import_delete_items Delete feed items and set report
feed_import_edit_feed_form Edit feed form
feed_import_edit_feed_form_submit Edit feed form submit
feed_import_edit_feed_form_validate Edit feed form validate
feed_import_edit_filter_form Edit filter form
feed_import_edit_filter_form_submit Edit filter form submit
feed_import_edit_filter_form_validate Edit filter form validate
feed_import_entity_delete Implements hook_entity_delete().
feed_import_export_feed_page Export feed
feed_import_feed_import_process_info Implements hook_feed_import_process_info().
feed_import_generate_xpath_item Generate field
feed_import_help Implements hook_help().
feed_import_import_feed_form Feed import form
feed_import_import_feed_form_submit Feed import form submit
feed_import_import_feed_form_validate Feed import form validate
feed_import_import_items Import feed and set report
feed_import_list_feeds List all feeds
feed_import_machine_name_exists Checks if a machine name already exists. Callback for machine_name input type.
feed_import_menu Implements hook_menu().
feed_import_new_filter Return new filter elements
feed_import_permission Implements hook_permision().
feed_import_process_feed Processes a feed
feed_import_reorder_fields_form Re-order fields form.
feed_import_reorder_fields_form_submit Re-order fields form submit.
feed_import_settings_form Settings form
feed_import_settings_form_validate Settings form validate
feed_import_sort_filter_by_weight usort() callback, for sorting filters by weight
feed_import_theme Implements hook_theme().
theme_feed_import_edit_filter_form Theme form feed_import_edit_filter_form
theme_feed_import_reorder_fields_form Theme form feed_import_edit_filter_form

Constants

Namesort descending Description
FEED_IMPORT_PATH This is path to feed import UI