You are here

FeedImport.php in Feed Import 8

File

feed_import_base/src/FeedImport.php
View source
<?php

namespace Drupal\feed_import_base;

use Drupal\field\Entity\FieldStorageConfig;

/**
 * This class provides helper functions for feed import.
 */
class FeedImport {

  // Constants
  const FEED_OK = 1;
  const FEED_SOURCE_ERR = 2;
  const FEED_ITEMS_ERR = 3;
  const FEED_OVERLAP_ERR = 4;
  const FEED_CONFIG_ERR = 5;

  // Entity info cache.
  protected static $entityInfo = array();

  // Default compare function.

  /**
   * Gets info about an entity.
   * This info is cached.
   *
   * @param string $entity_name The entity name
   *
   * @return object An object that describes entity
   */
  public static function getEntityInfo($entity_name) {
    if (empty($entity_name)) {
      return FALSE;
    }
    elseif (isset(static::$entityInfo[$entity_name])) {
      return static::$entityInfo[$entity_name];
    }
    if (!($entity = \Drupal::entityTypeManager()
      ->getDefinition($entity_name))) {
      return FALSE;
    }

    // Set main entity info.
    $info = (object) array(
      'name' => $entity_name,
      'idKey' => $entity
        ->getKey('id') ? $entity
        ->getKey('id') : NULL,
      'langKey' => $entity
        ->getKey('langcode') ? $entity
        ->getKey('langcode') : 'langcode',
      'bundleKey' => $entity
        ->getKey('bundle') ? $entity
        ->getKey('bundle') : NULL,
      'controller' => \Drupal::entityTypeManager()
        ->getStorage($entity_name),
      'properties' => array(),
      'fields' => array(),
    );
    if ($info->controller) {
      $info->controller->canSave = method_exists($info->controller, 'save');
      $info->controller->canDelete = method_exists($info->controller, 'delete');

      // TODO: look into this.
      // $info->controller->canResetCache = method_exists($info->controller, 'resetCache');
    }
    else {
      $info->controller = (object) array(
        'canCreate' => FALSE,
        'canSave' => FALSE,
        'canDelete' => FALSE,
        'canResetCache' => FALSE,
      );
    }
    $bundles = \Drupal::service('entity_type.bundle.info')
      ->getBundleInfo($entity_name);

    // Get fields info.
    foreach (array_keys($bundles) as $bundle_name) {
      if ($fieldlist = \Drupal::service('entity_field.manager')
        ->getFieldDefinitions($entity_name, $bundle_name)) {
        foreach ($fieldlist as &$field) {
          $field_info = FieldStorageConfig::loadByName($entity_name, $field
            ->getName());
          if ($field_info && (method_exists($field, 'isDeleted') && !$field
            ->isDeleted()) && !isset($info->fields[$field
            ->getName()])) {
            $info->fields[$field
              ->getName()] = array(
              'name' => $field
                ->getName(),
              'column' => key($field_info
                ->getSchema()['columns']),
              'columns' => array_keys($field_info
                ->getSchema()['columns']),
              'cardinality' => $field_info
                ->getCardinality(),
              'type' => $field
                ->getType(),
              'module' => $field_info
                ->getTypeProvider(),
            );
          }
          else {
            if (!isset($info->properties[$field
              ->getName()])) {
              $info->properties[$field
                ->getName()] = $field
                ->getName();
            }
          }
        }
      }
    }
    return static::$entityInfo[$entity_name] = $info;
  }

  /**
   * Cache used for entity names.
   */
  protected static $entityNames = array();

  /**
   * Get all entity names.
   *
   * @return array
   *    List of all entity names.
   */
  public static function getAllEntities() {
    if (!static::$entityNames) {
      foreach (\Drupal::entityManager()
        ->getDefinitions() as $entity => $info) {
        static::$entityNames[$entity] = $info
          ->getLabel();
      }
    }
    return static::$entityNames;
  }

  // Default field compare function.
  public static $defaultFieldCompareFunction = '_feed_import_base_compare_other_fields';

  /**
   * Sets processor settings.
   *
   * @param FeedImportProcessor $fi
   *    The processor
   * @param object $feed
   *     Feed settings
   * @param string $filter_dir
   *     Path to extra filters dir
   *
   * @return FeedImportProcessor
   *     An instance of FeedImportProcessor or FALSE on error
   */
  public static function setProcessorSettings(FeedImportProcessor $fi, $feed, $filter_dir) {
    $s = $feed->settings;
    $ok = TRUE;
    $ok = $ok && $fi
      ->setEntityInfo(static::getEntityInfo($feed->entity));
    $ok = $ok && $fi
      ->setOptions($s['processor']['options']);
    if (!isset($s['functions'])) {
      $s['functions'] = NULL;
    }
    $ok = $ok && $fi
      ->setFilter(new $s['filter']['class']($filter_dir, $s['filter']['options']), $s['functions']);
    $ok = $ok && $fi
      ->setReader(new $s['reader']['class']($s['reader']['options']));
    $hm = new $s['hashes']['class']($feed->entity, $feed->machine_name);
    $hm
      ->setOptions($s['hashes']['options']);
    $ok = $ok && $fi
      ->setHashManager($hm);
    $ok = $ok && $fi
      ->setUniq($s['uniq_path']);
    $ok = $ok && $fi
      ->setFields($s['fields'], $s['static_fields'], static::getFieldsCompareFunctions(), static::getMergeFieldClasses(), static::$defaultFieldCompareFunction);
    $ok = $ok && $fi
      ->setCustomSettings(array_diff_key($s, array(
      'uniq_path' => 1,
      'processor' => 1,
      'reader' => 1,
      'hashes' => 1,
      'filter' => 1,
      'fields' => 1,
      'static_fields' => 1,
      'feed' => 1,
      'functions' => 1,
    )));
    return $ok ? TRUE : $fi
      ->getErrors();
  }

  /**
   * Returns field compare functions.
   */
  public static function getFieldsCompareFunctions() {
    $funcs = \Drupal::moduleHandler()
      ->invokeAll('feed_import_field_compare_functions');
    foreach ($funcs as $type => &$f) {
      if (is_array($f)) {
        for ($i = 0, $max = count($f); $i < $max; $i++) {
          if (is_callable($f[$i])) {
            $f = $f[$i];
            continue 2;
          }
        }
        unset($funcs[$type]);
      }
      elseif (!is_callable($f)) {
        unset($funcs[$type]);
      }
    }
    return $funcs;
  }

  /**
   * Gets the classes used to merge field values.
   *
   * @return array
   *    An array of classes keyed by merge mode.
   */
  public static function getMergeFieldClasses() {
    $classes = \Drupal::moduleHandler()
      ->invokeAll('feed_import_field_merge_classes');
    foreach ($classes as $key => &$class) {
      if (isset($class['class'])) {
        $class = $class['class'];
      }
      else {
        unset($classes[$key]);
      }
    }
    return $classes;
  }

  // A list of all feeds configuration.
  protected static $feedsList = array();

  /**
   * Get a list with all feeds.
   */
  public static function loadAllFeeds() {
    if (static::$feedsList) {
      return static::$feedsList;
    }
    $feeds = db_select('feed_import_settings', 'f')
      ->fields('f')
      ->orderBy('id', 'DESC')
      ->execute()
      ->fetchAll();
    $ret = array();
    foreach ($feeds as &$feed) {
      $feed->settings = unserialize($feed->settings);
      $ret[$feed->machine_name] = $feed;
    }
    return static::$feedsList = $ret;
  }

  /**
   * Loads a feed from database
   *
   * @param int|string $name
   *    Feed machine name or id
   *
   * @return object
   *    Feed object or FALSE
   */
  public static function loadFeed($name) {
    $feed = db_select('feed_import_settings', 'f')
      ->fields('f')
      ->condition((int) $name ? 'id' : 'machine_name', $name, '=')
      ->range(0, 1)
      ->execute()
      ->fetchObject();
    if (!$feed) {
      return FALSE;
    }
    $feed->settings = unserialize($feed->settings);
    return $feed;
  }

  /**
   * Deletes a feed.
   *
   * @param object $feed
   *    Feed info
   * @param bool $hashes
   *    Also remove hashes
   */
  public static function deleteFeed($feed, $hashes = TRUE) {
    db_delete('feed_import_settings')
      ->condition('machine_name', $feed->machine_name)
      ->execute();
    if ($hashes && isset($feed->settings['hashes']['class'])) {
      $class = $feed->settings['hashes']['class'];
      $class::deleteByFeed($feed->machine_name);
    }
  }

  /**
   * Gets a new empty feed configuration.
   *
   * @return array
   *     An empty feed configuration.
   */
  public static function getEmptyFeed() {
    return array(
      'name' => NULL,
      'machine_name' => NULL,
      'entity' => NULL,
      'cron_import' => 0,
      'last_run' => 0,
      'last_run_duration' => 0,
      'last_run_items' => 0,
      'settings' => array(
        'uniq_path' => NULL,
        'preprocess' => NULL,
        'feed' => array(
          'protect_on_invalid_source' => FALSE,
          'protect_on_fewer_items' => 0,
        ),
        'processor' => array(
          'name' => 'default',
          'class' => 'Drupal\\feed_import_base\\FeedImportProcessor',
          'options' => array(
            'items_count' => 0,
            'skip_imported' => FALSE,
            'reset_cache' => 100,
            'break_on_undefined_filter' => TRUE,
            'skip_defined_functions_check' => FALSE,
            'updates_only' => FALSE,
          ),
        ),
        'reader' => array(
          'name' => 'xml',
          'class' => 'Drupal\\feed_import_base\\SimpleXMLFIReader',
          'options' => array(),
        ),
        'hashes' => array(
          'name' => 'sql',
          'class' => 'Drupal\\feed_import_base\\FeedImportSQLHashes',
          'options' => array(
            'ttl' => 0,
            'insert_chunk' => 300,
            'update_chunk' => 300,
            'group' => '',
          ),
        ),
        'filter' => array(
          'name' => 'default',
          'class' => 'Drupal\\feed_import_base\\FeedImportMultiFilter',
          'options' => array(
            'param' => '[field]',
            'include' => NULL,
          ),
        ),
        'fields' => array(),
        'static_fields' => array(),
        'functions' => array(),
      ),
    );
  }

  /**
   * Saves a feed in database
   *
   * @param object $feed
   *    Feed info
   */
  public static function saveFeed($feed) {
    if (empty($feed->name) || empty($feed->machine_name) || empty($feed->entity) || empty($feed->settings)) {
      return FALSE;
    }
    if (!isset($feed->cron_import)) {
      $feed->cron_import = 0;
    }
    $fields = array(
      'name' => $feed->name,
      'machine_name' => $feed->machine_name,
      'entity' => $feed->entity,
      'cron_import' => (int) $feed->cron_import,
      'settings' => serialize($feed->settings),
    );
    if (isset($feed->id)) {
      db_update('feed_import_settings')
        ->fields($fields)
        ->condition('id', $feed->id)
        ->execute();
    }
    else {
      $fields += array(
        'last_run' => 0,
        'last_run_duration' => 0,
        'last_run_items' => 0,
      );
      db_insert('feed_import_settings')
        ->fields($fields)
        ->execute();
    }
    return TRUE;
  }

  /**
   * Saves feed import status
   */
  public static function saveFeedImportStatus($feed) {
    db_update('feed_import_settings')
      ->fields(array(
      'last_run' => (int) $feed->last_run,
      'last_run_duration' => (int) $feed->last_run_duration,
      'last_run_items' => (int) $feed->last_run_items,
    ))
      ->condition('machine_name', $feed->machine_name)
      ->execute();
  }

  // Active import
  public static $activeImport = NULL;

  /**
   * Imports a feed.
   *
   * @param object $feed
   *    Feed info
   * @param string $filters_dir
   *    Path to filters dir
   */
  public static function import($feed, $filters_dir) {
    if (!empty($feed->settings['preprocess']) && function_exists($feed->settings['preprocess'])) {

      // Preprocess feed before import.
      call_user_func($feed->settings['preprocess'], $feed);
    }
    $class = $feed->settings['processor']['class'];
    $fi = new $class($feed->machine_name);
    $fi
      ->setErrorHandler(TRUE);
    $f = static::setProcessorSettings($fi, $feed, $filters_dir);
    if ($f !== TRUE) {
      $fi
        ->setErrorHandler(FALSE);
      return array(
        'init_error' => TRUE,
        'errors' => $f,
      );
    }
    unset($f);
    $lastimport = static::$activeImport;
    static::$activeImport = $fi;
    $f = $fi
      ->process();
    static::$activeImport = $lastimport;
    $fi
      ->setErrorHandler(FALSE);
    return $f;
  }

  /**
   * Gets all hash manager classes used by feeds.
   *
   * @return array
   *    An array of classes
   */
  public static function getHashManagers() {
    static $hm = array();
    if (!$hm) {
      foreach (static::loadAllFeeds() as $feed) {
        if (!empty($feed->settings['hashes']['class'])) {
          $hm[] = $feed->settings['hashes']['class'];
        }
      }
      $hm = array_unique($hm);
    }
    return $hm;
  }

  /**
   * Delete all expired items.
   *
   * @param int $max
   *    Max number of entity ids to delete from
   *    a hash manager
   *
   * @return int
   *    Number of deleted entities
   */
  public static function deleteExpired($max = 0) {
    $deleted = 0;
    foreach (static::loadAllFeeds() as $feed) {
      if (empty($feed->settings['hashes']['class'])) {
        continue;
      }
      $class = $feed->settings['hashes']['class'];
      $items = $class::getExpired($feed->machine_name, $max);
      foreach ($items as $e => &$ids) {
        $entity = static::getEntityInfo($e);

        // Delete entities.
        if ($entity->deleteCallback) {
          $f = $entity->deleteCallback . '_multiple';
          if (function_exists($f)) {
            $f($ids);
          }
          else {
            array_map($entity->deleteCallback, $ids);
          }
        }
        else {
          $entity->controller
            ->delete($ids);
        }

        // Delete hashes.
        $class::delete(array_keys($ids));
        $deleted += count($ids);
        unset($items[$e], $entity);
      }
    }
    return $deleted;
  }

  // Deleted entities.
  protected static $deletedEntities = array();

  /**
   * Schedule a deleted entity to be removed from hashes.
   *
   * @param string $entity_type
   *    Entity type
   * @param int $id
   *    Entity id
   */
  public static function addDeletedEntity($entity_type, $id) {
    static $unregistered = TRUE;
    if ($unregistered) {
      $unregistered = FALSE;
      register_shutdown_function(array(
        __CLASS__,
        'removeEntityHashes',
      ));
    }
    static::$deletedEntities[$entity_type][] = $id;
  }

  /**
   * Removes entity hashes.
   */
  public static function removeEntityHashes($entities = NULL) {
    if ($entities == NULL) {
      $entities =& static::$deletedEntities;
    }
    foreach (static::getHashManagers() as $hm) {
      array_walk($entities, array(
        $hm,
        'deleteEntities',
      ));
    }
    $entities = NULL;
  }

}

Classes

Namesort descending Description
FeedImport This class provides helper functions for feed import.