You are here

feed_import_base.module in Feed Import 8

Same filename and directory in other branches
  1. 7.3 feed_import_base/feed_import_base.module

Basic settings for feed import base module

File

feed_import_base/feed_import_base.module
View source
<?php

/**
 * @file
 * Basic settings for feed import base module
 */
use Drupal\feed_import_base\FeedImportProcessor;
use Drupal\feed_import_base\FeedImport;

/**
 * Implements hook_help().
 */
function feed_import_base_help($path, $arg) {
  if ($path == 'admin/help#feed_import_base') {
    $vars = array(
      '!project_page' => \Drupal::l('Feed Import', \Drupal\Core\Url::fromUri('http://drupal.org/project/feed_import')),
    );
    $help = t('The basic functionality used to import content into entities.');
    $help .= '<br />';
    $help .= t('For more info please read README.txt or go to !project_page.', $vars);
    return $help;
  }
}

/**
 * Implements hook_permision().
 */
function feed_import_base_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('Run feed import'),
      'description' => t('Import a feed'),
    ),
  );
}

/**
 * Implements hook_menu().
 */
function feed_import_base_menu() {
  $items = array();
  $items['admin/config/services/feed_import/settings'] = array(
    'title' => 'Settings',
    'description' => 'Manages feed import global settings',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'feed_import_base_settings_form',
    ),
    'access callback' => 'user_access',
    'access arguments' => array(
      'feed import',
    ),
    'type' => MENU_LOCAL_ACTION,
    'weight' => 10,
  );
  return $items;
}

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

  // Check if cron import is enabled.
  // @FIXME
  // // @FIXME
  // // This looks like another module's variable. You'll need to rewrite this call
  // // to ensure that it uses the correct configuration object.
  // if (variable_get('feed_import_use_cron', FALSE)) {
  //     $overlap = variable_get('feed_import_let_overlap', array());
  //     $running = variable_get('feed_import_import_running', array());
  //     // Check if we can import.
  //     if (_feed_import_base_cron_in_time()) {
  //       $feeds = array_filter(FeedImport::loadAllFeeds(), '_feed_import_base_feed_enabled');
  //       uasort($feeds, '_feed_import_base_sort_feeds');
  //       $to_import = NULL;
  //       foreach ($feeds as $name => $feed) {
  //         if (in_array($feed->entity, $overlap) ||
  //             empty($running[$feed->entity]) ||
  //             !in_array($name, $running[$feed->entity])) {
  //           $to_import = $feed;
  //           break;
  //         }
  //       }
  //       unset($feeds, $feed);
  //       if ($to_import) {
  //         _feed_import_base_process_feed($to_import);
  //         variable_set('feed_import_last_executed_import', REQUEST_TIME);
  //       }
  //     }
  //   }
  // Delete expired items.
  // @FIXME
  // // @FIXME
  // // This looks like another module's variable. You'll need to rewrite this call
  // // to ensure that it uses the correct configuration object.
  // if (($d = variable_get('feed_import_delete_items_per_cron', 300)) > 0) {
  //     $d = FeedImport::deleteExpired($d);
  //     if ($d && variable_get('feed_import_reports', TRUE)) {
  //       watchdog('Feed Import', 'Deleted @count expired items', array(
  //       '@count' => $d,
  //       ), WATCHDOG_NOTICE);
  //     }
  //   }
}

/**
 * Imports a feed.
 */
function _feed_import_base_process_feed($feed, $recheck = FALSE) {
  $state = \Drupal::state();
  $config = \Drupal::config('feed_import_base.settings');
  $running = $state
    ->get('feed_import.running', array());
  $invoke_hooks = $config
    ->get('invoke_hooks', FALSE);
  $overlap = $config
    ->get('let_overlap');
  if ($recheck) {
    if (is_array($overlap) && !in_array($feed->entity, $overlap) && !empty($running[$feed->entity]) && in_array($feed->machine_name, $running[$feed->entity])) {

      // Invoke hooks.
      $invoke_hooks && \Drupal::moduleHandler()
        ->invokeAll('feed_import_error', [
        FeedImport::FEED_OVERLAP_ERR,
        $feed,
        array(),
      ]);
      return FeedImport::FEED_OVERLAP_ERR;
    }
    unset($overlap);
  }
  $running[$feed->entity][] = $feed->machine_name;
  $state
    ->set('feed_import.running', $running);
  unset($running);
  register_shutdown_function('_feed_import_base_remove_running', $feed);
  $report = FeedImport::import($feed, _feed_import_base_get_filters_dir());
  _feed_import_base_remove_running($feed);
  $feed->skip_remove_running = TRUE;
  $report_errors = $config
    ->get('feed_import_reports');

  // Check for errors.
  if (empty($report['init_error'])) {

    // Save last run and duration.
    $feed->last_run = $report['started'];
    $d = $report['finished'] - $report['started'];
    $feed->last_run_duration = $d > 0 ? $d : 0;

    // Check fewer items protection.
    if (strlen($feed->settings['uniq_path']) && $feed->settings['feed']['protect_on_fewer_items']) {
      $d = trim($feed->settings['feed']['protect_on_fewer_items']);
      if (substr($d, -1) == '%') {

        // Do not update last import items if percentage is used!
        $d = rtrim($d, '%') * $feed->last_run_items / 100;
      }
      else {
        $feed->last_run_items = $report['total'];
      }
      $d = (int) $d;
      if ($report['total'] < $d) {

        // Save import status.
        FeedImport::saveFeedImportStatus($feed);

        // Reschedul all.
        $class = $feed->settings['hashes']['class'];
        $class::rescheduleAll($feed->machine_name, $feed->settings['hashes']['options']['ttl']);
        if ($report_errors) {

          // Report base info.
          _feed_import_base_save_report($report, $feed);

          // Report rescheduled all.
          \Drupal::logger('Feed Import')
            ->warning('Rescheduled all items for @name because source contained only @total items but expected @expected. !errors', array(
            '@name' => $feed->name,
            '@total' => $report['total'],
            '@expected' => $d,
            '!errors' => '<br />' . _feed_import_base_get_error_table($report['errors']),
          ));
        }
        $report['expected'] = $d;

        // Invoke hooks.
        $invoke_hooks && \Drupal::moduleHandler()
          ->invokeAll('feed_import_error', [
          FeedImport::FEED_ITEMS_ERR,
          $feed,
          $report,
        ]);
        return FeedImport::FEED_ITEMS_ERR;
      }
    }
    $feed->last_run_items = $report['total'];
    FeedImport::saveFeedImportStatus($feed);

    // Save report if needed.
    $report_errors && _feed_import_base_save_report($report, $feed);

    // Invoke hooks.
    $invoke_hooks && \Drupal::moduleHandler()
      ->invokeAll('feed_import_success', [
      $feed,
      $report,
    ]);
    return FeedImport::FEED_OK;
  }
  else {
    $ret = FALSE;

    // Reader init problem.
    if (strlen($feed->settings['uniq_path']) && $feed->settings['feed']['protect_on_invalid_source']) {
      $ret = TRUE;
      $feed->last_run = time();
      $feed->last_run_duration = 0;
      if (substr(trim($feed->settings['feed']['protect_on_fewer_items']), -1) != '%') {
        $feed->last_run_items = 0;
      }
      FeedImport::saveFeedImportStatus($feed);

      // Reschedule all.
      $class = $feed->settings['hashes']['class'];
      $class::rescheduleAll($feed->machine_name, $feed->settings['hashes']['options']['ttl']);
      if ($report_errors) {

        // Report reschedule all.
        \Drupal::logger('Feed Import')
          ->warning('Rescheduled all items for @name due to a source problem. !errors', array(
          '@name' => $feed->name,
          ':errors' => '<br />' . _feed_import_base_get_error_table($report['errors']),
        ));
      }
    }

    // Invoke hooks.
    $invoke_hooks && \Drupal::moduleHandler()
      ->invokeAll('feed_import_error', [
      FeedImport::FEED_SOURCE_ERR,
      $feed,
      $report,
    ]);
    if ($ret) {
      return FeedImport::FEED_SOURCE_ERR;
    }
    else {
      $invoke_hooks = FALSE;
    }
  }
  if ($report_errors) {
    dpm(_feed_import_base_get_error_table($report['errors']));
    \Drupal::logger('Feed Import')
      ->warning('Cannot process feed @name because it is misconfigured. :errors', [
      '@name' => $feed->name,
      ':errors' => _feed_import_base_get_error_table($report['errors']),
    ]);
  }

  // Invoke hooks.
  $invoke_hooks && \Drupal::moduleHandler()
    ->invokeAll('feed_import_error', [
    FeedImport::FEED_CONFIG_ERR,
    $feed,
    $report,
  ]);
  return FeedImport::FEED_CONFIG_ERR;
}

/**
 * Removes a feed from running array.
 */
function _feed_import_base_remove_running($feed) {
  if (isset($feed->skip_remove_running)) {
    unset($feed->skip_remove_running);
    return;
  }
  $state = \Drupal::state();
  $running = $state
    ->get('feed_import.running', array());
  if (!empty($running[$feed->entity]) && ($pos = array_search($feed->machine_name, $running[$feed->entity])) !== FALSE) {
    unset($running[$feed->entity][$pos]);
    $state
      ->set('feed_import.running', $running);
  }
}

/**
 * Saves report in log.
 *
 * @param array $r
 *    Report array
 * @param object $feed
 *    Related feed
 */
function _feed_import_base_save_report($r, $feed) {

  // @FIXME
  // theme() has been renamed to _theme() and should NEVER be called directly.
  // Calling _theme() directly can alter the expected output and potentially
  // introduce security issues (see https://www.drupal.org/node/2195739). You
  // should use renderable arrays instead.
  //
  //
  // @see https://www.drupal.org/node/2195739
  // $info = theme('table', array(
  //     'header' => array(
  //       t('Feed name'),
  //       t('Duration'),
  //       t('Found'),
  //       t('New'),
  //       t('Updated'),
  //       t('Rescheduled'),
  //       t('Skipped'),
  //       t('Skipped protected'),
  //       t('New protected'),
  //       t('Missing entities'),
  //     ),
  //     'rows' => array(array(
  //       $feed->name,
  //       gmdate('H:i:s', $r['finished'] - $r['started']) . '<br>' . format_date($r['started']) . ' - ' . format_date($r['finished']),
  //       $r['total'],
  //       $r['new'],
  //       $r['updated'],
  //       $r['rescheduled'],
  //       $r['skipped'],
  //       $r['protected_skipped'],
  //       $r['protected'],
  //       $r['missing'],
  //     )),
  //   ));
  $err = _feed_import_base_get_error_table($r['errors']);
  \Drupal::logger('Feed Import')
    ->notice('Feed @name imported !status Errors !errors', array(
    '@name' => $feed->name,
    '!status' => '<br>' . $info,
    '!errors' => '<br>' . $err,
  ));
}

/**
 * Returns a table containig error messages.
 */
function _feed_import_base_get_error_table($errors) {
  $table = [
    '#type' => 'table',
    '#header' => array(
      t('Error'),
      t('Error number'),
      t('Line'),
      t('File'),
    ),
    '#rows' => $errors,
    '#empty' => t('No errors reported'),
    '#caption' => t('Errors'),
  ];

  // $output = render($table)->__toString();
  return $table;
}

/**
 * Gets a variable directly from table.
 * @see variable_get()
 */
function _feed_import_base_variable_get($name, $default = NULL) {
  $name = db_query('SELECT value FROM {variable} WHERE name=:name', array(
    ':name' => $name,
  ))
    ->fetchColumn(0);
  if (!$name) {
    return $default;
  }
  return unserialize($name);
}

/**
 * Checks if a feed is enabled.
 */
function _feed_import_base_feed_enabled($feed) {
  return $feed->cron_import > 0;
}

/**
 * Sort callback for feeds.
 */
function _feed_import_base_sort_feeds($a, $b) {
  return $a->last_run - $b->last_run;
}

/**
 * Returns curent filters dir.
 */
function _feed_import_base_get_filters_dir() {
  $config = \Drupal::config('feed_import_base.settings');
  $dir = $config
    ->get('filters_dir');
  if (empty($dir)) {
    $dir = drupal_get_path('module', 'feed_import_base') . '/filters';
  }
  return $dir;
}

/**
 * Helper for cron.
 *
 * @return bool
 *    Cron can run
 */
function _feed_import_base_cron_in_time() {

  // @FIXME
  // // @FIXME
  // // This looks like another module's variable. You'll need to rewrite this call
  // // to ensure that it uses the correct configuration object.
  // if (variable_get('feed_import_time_settings', 0)) {
  //     // 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);
  //     return $time1 < $time2 && $time1 <= REQUEST_TIME && REQUEST_TIME <= $time2;
  //   }
  //   else {
  //     $last_executed = variable_get('feed_import_last_executed_import', 0);
  //     $time_between = variable_get('feed_import_time_between_imports', 3600);
  //     return ($last_executed + $time_between) < REQUEST_TIME;
  //   }
}

/**
 * Settings form
 */
function feed_import_base_settings_form($form, &$form_state) {

  // @FIXME
  // // @FIXME
  // // This looks like another module's variable. You'll need to rewrite this call
  // // to ensure that it uses the correct configuration object.
  // $form['feed_import_reports'] = array(
  //     '#type' => 'checkbox',
  //     '#default_value' => variable_get('feed_import_reports', TRUE),
  //     '#title' => t('Provide import reports'),
  //     '#description' => t('These are log reports.'),
  //   );
  // @FIXME
  // // @FIXME
  // // This looks like another module's variable. You'll need to rewrite this call
  // // to ensure that it uses the correct configuration object.
  // $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,
        ),
      ),
    ),
  );

  // @FIXME
  // // @FIXME
  // // This looks like another module's variable. You'll need to rewrite this call
  // // to ensure that it uses the correct configuration object.
  // $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'),
  //   );
  // @FIXME
  // // @FIXME
  // // This looks like another module's variable. You'll need to rewrite this call
  // // to ensure that it uses the correct configuration object.
  // $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),
  //       ),
  //     ),
  //   );
  // @FIXME
  // // @FIXME
  // // This looks like another module's variable. You'll need to rewrite this call
  // // to ensure that it uses the correct configuration object.
  // $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),
  //       ),
  //     ),
  //   );
  // @FIXME
  // // @FIXME
  // // This looks like another module's variable. You'll need to rewrite this call
  // // to ensure that it uses the correct configuration object.
  // $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),
  //       ),
  //     ),
  //   );
  // @FIXME
  // // @FIXME
  // // This looks like another module's variable. You'll need to rewrite this call
  // // to ensure that it uses the correct configuration object.
  // $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.'),
  //     '#required' => TRUE,
  //   );
  $form['feed_import_filters_dir'] = array(
    '#type' => 'textfield',
    '#default_value' => _feed_import_base_get_filters_dir(),
    '#title' => t('Extra filters base path'),
    '#description' => t('Where to look for filter files.'),
  );

  // @FIXME
  // // @FIXME
  // // This looks like another module's variable. You'll need to rewrite this call
  // // to ensure that it uses the correct configuration object.
  // $form['feed_import_let_overlap'] = array(
  //     '#type' => 'select',
  //     '#multiple' => TRUE,
  //     '#options' => FeedImport::getAllEntities(),
  //     '#default_value' => variable_get('feed_import_let_overlap', array()),
  //     '#title' => t('Allow import overlap for specified entities'),
  //     '#description' => t('This is not indicated for nodes.'),
  //   );
  // @FIXME
  // // @FIXME
  // // This looks like another module's variable. You'll need to rewrite this call
  // // to ensure that it uses the correct configuration object.
  // $form['feed_import_invoke_hooks'] = array(
  //     '#type' => 'checkbox',
  //     '#default_value' => variable_get('feed_import_invoke_hooks', FALSE),
  //     '#title' => t('Invoke hooks on import sucess or error'),
  //     '#description' => t('This can be useful for sending alerts.'),
  //   );
  return system_settings_form($form);
}

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

  // 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.'));
    }
  }

  // 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.'));
    }
  }
}

/**
 * Implements hook_entity_delete().
 */
function feed_import_base_entity_delete($entity, $type) {
  return;
  $e = FeedImport::getEntityInfo($type);
  if (isset($entity->{$e->idKey})) {
    $id = $entity->{$e->idKey};
    $id && is_numeric($id) && FeedImport::addDeletedEntity($type, $id);
  }
}

/**
 * Implements hook_feed_import_field_merge_classes().
 */
function feed_import_base_feed_import_field_merge_classes() {
  return array(
    FeedImportProcessor::UPDATE_COMBINE => array(
      'title' => t('Merge field - no duplicates'),
      'description' => t('Tries to avoid updates by checking if field values already exists.'),
      'class' => 'Drupal\\feed_import_base\\FeedImportMergeNoDuplicates',
    ),
    FeedImportProcessor::UPDATE_MERGE => array(
      'title' => t('Merge field - allow duplicates'),
      'description' => t('Appends new values to field.'),
      'class' => 'Drupal\\feed_import_base\\FeedImportMergeDuplicates',
    ),
    FeedImportProcessor::UPDATE_OVERWRITE => array(
      'title' => t('Overwrite field'),
      'description' => t('Overwrites field values if are distinct. Field will be removed if there are no values.'),
      'class' => 'Drupal\\feed_import_base\\FeedImportMergeOverwrite',
    ),
    FeedImportProcessor::UPDATE_OVERWRITE_FAST => array(
      'title' => t('Fast overwrite field'),
      'description' => t('Always overwrites field values. Field will NOT be removed if there are no values.'),
      'class' => 'Drupal\\feed_import_base\\FeedImportMergeOverwriteFast',
    ),
  );
}

/**
 * Implements hook_feed_import_field_compare_functions().
 */
function feed_import_base_feed_import_field_compare_functions() {
  return array(
    'image:image' => '_feed_import_base_compare_image_field',
    'taxonomy:taxonomy_term_reference' => '_feed_import_base_compare_taxonomy_term_reference_field',
  );
}

/**
 * Field compare any.
 *
 * @param array $new New field data
 * @param array $current Current field data
 *
 * @return bool True if they are the same
 */
function _feed_import_base_compare_other_fields(&$new, &$current) {
  return !array_diff_assoc($new, $current);
}

/**
 * Field compare callback for image:image.
 */
function _feed_import_base_compare_image_field(&$new, &$current) {
  return $new['fid'] == $current['fid'];
}

/**
 * Field compare callback for taxonomy:taxonomy_term_reference.
 */
function _feed_import_base_compare_taxonomy_term_reference_field(&$new, &$current) {
  return $new['tid'] == $current['tid'];
}

Functions

Namesort descending Description
feed_import_base_cron Implements hook_cron().
feed_import_base_entity_delete Implements hook_entity_delete().
feed_import_base_feed_import_field_compare_functions Implements hook_feed_import_field_compare_functions().
feed_import_base_feed_import_field_merge_classes Implements hook_feed_import_field_merge_classes().
feed_import_base_help Implements hook_help().
feed_import_base_menu Implements hook_menu().
feed_import_base_permission Implements hook_permision().
feed_import_base_settings_form Settings form
feed_import_base_settings_form_validate Settings form validate
_feed_import_base_compare_image_field Field compare callback for image:image.
_feed_import_base_compare_other_fields Field compare any.
_feed_import_base_compare_taxonomy_term_reference_field Field compare callback for taxonomy:taxonomy_term_reference.
_feed_import_base_cron_in_time Helper for cron.
_feed_import_base_feed_enabled Checks if a feed is enabled.
_feed_import_base_get_error_table Returns a table containig error messages.
_feed_import_base_get_filters_dir Returns curent filters dir.
_feed_import_base_process_feed Imports a feed.
_feed_import_base_remove_running Removes a feed from running array.
_feed_import_base_save_report Saves report in log.
_feed_import_base_sort_feeds Sort callback for feeds.
_feed_import_base_variable_get Gets a variable directly from table.