You are here

entityreference_feeds.module in Entity reference feeds 7

This is the main module file for entity reference feeds.

File

entityreference_feeds.module
View source
<?php

/**
 * @file
 * This is the main module file for entity reference feeds.
 */
function _entityreference_feeds_get_targets($field_name, $target_type, $target_bundle) {
  $targets_cache =& drupal_static('entityreference_feeds_feeds_processor_targets', array());
  $entity_info = entity_get_info($target_type);
  if (!isset($targets_cache[$field_name][$target_type][$target_bundle])) {
    $info = array(
      'bundle' => $target_bundle,
    );
    $_targets = array();
    $target_key = $field_name . ':' . $target_bundle;
    $wrapper = entity_metadata_wrapper($target_type, NULL, $info);

    // @todo: maybe restrict to data types feeds can deal with.
    foreach ($wrapper
      ->getPropertyInfo() as $name => $property_info) {

      /* We leave fields to feeds mappers in accordance with the principle of
            least astonishment. They are also more forgiving and "robust" in some
            cases, for file/image-fields for example. Node targets for properties
            like "title" etc are inaccessable to us as they are found in the
            node-processor. Besides, using entity-api for this is a much more
            generic approach and will work for more entity-types than just node
         */
      if (!isset($property_info['field']) || !$property_info['field']) {
        if (!empty($property_info['setter callback'])) {
          $_targets[$target_key . ':entityreference_feeds:' . $name] = array(
            'name' => t('@field_name (@bundle: @target)', array(
              '@field_name' => $field_name,
              '@target' => $property_info['label'],
              '@bundle' => $target_bundle,
            )),
            'description' => isset($property['description']) ? t('@field_name: @target in @type type @bundle', array(
              '@field_name' => $field_name,
              '@target' => $property_info['label'],
              '@type' => $target_type,
              '@bundle' => $target_bundle,
            )) : NULL,
            'callback' => 'entityreference_feeds_feeds_set_target',
          );
        }
      }
    }

    // Add general GUID target.
    $_targets[$target_key . ':entityreference_feeds:guid'] = array(
      'name' => t('@field_name (@bundle: @target)', array(
        '@field_name' => $field_name,
        '@target' => t('GUID'),
        '@bundle' => $target_bundle,
      )),
      'description' => t('The globally unique identifier of the item. E. g. the feed item GUID in the case of a syndication feed.'),
      //'real_target' => $field_name,

      //'optional_unique' => FALSE, //@todo set this to false?
      'callback' => 'entityreference_feeds_feeds_set_target',
    );

    // @todo: this is ugly as hell, but needed because of limitations of
    // drupal_alter (taking a maximum of 3 parameters). Should do the job
    // though, hopefully with no side-effects
    $entity_targets = array(
      'entityreference_feeds_processed' => array(),
    );

    // Let other modules expose mapping targets.
    $entity_targets += module_invoke_all('feeds_processor_targets', $target_type, $target_bundle);
    drupal_alter('feeds_processor_targets', $entity_targets, $target_type, $target_bundle);
    foreach ($entity_targets as $target_target => $entity_target) {
      if (isset($entity_target['name'])) {
        $entity_target['name'] = t('@field_name (@bundle: @target)', array(
          '@field_name' => $field_name,
          '@target' => $target_target,
          '@bundle' => $target_bundle,
        ));
      }
      if (isset($entity_target['description'])) {
        $entity_target['description'] = t('@field_name: @target in @type type @bundle', array(
          '@field_name' => $field_name,
          '@target' => $target_target,
          '@type' => $target_type,
          '@bundle' => $target_bundle,
        ));
      }
      $callback = isset($entity_target['callback']) ? $entity_target['callback'] : 'entityreference_feeds';
      $entity_target['callback'] = 'entityreference_feeds_feeds_set_target';
      $_targets[$target_key . ':' . $callback . ':' . $target_target] = $entity_target;
    }
    if (!isset($targets_cache[$field_name])) {
      $targets_cache[$field_name] = array();
    }
    if (!isset($targets_cache[$field_name][$target_type])) {
      $targets_cache[$field_name][$target_type] = array();
    }
    $targets_cache[$field_name][$target_type][$target_bundle] = $_targets;
  }
  return $targets_cache[$field_name][$target_type][$target_bundle];
}

/**
 * Implements hook_feeds_processor_targets_alter().
 *
 * @see FeedsNodeProcessor::getMappingTargets()
 */
function entityreference_feeds_feeds_processor_targets_alter(&$targets, $entity_type, $bundle_name) {

  // Prevent infinite function call cycle.
  if (isset($targets['entityreference_feeds_processed'])) {
    unset($targets['entityreference_feeds_processed']);
    return;
  }
  foreach (field_info_instances($entity_type, $bundle_name) as $field_name => $field_instance) {
    $info = field_info_field($field_name);
    if ($info['type'] == 'entityreference') {
      $target_type = $info['settings']['target_type'];
      $target_bundles = $info['settings']['handler_settings']['target_bundles'];
      foreach ($target_bundles as $target_bundle) {
        $targets += _entityreference_feeds_get_targets($field_name, $target_type, $target_bundle);
      }
    }
  }
}
function entityreference_feeds_processor_mapped_targets($id) {
  $static_cache =& drupal_static(__FUNCTION__, array());
  if (isset($static_cache[$id])) {
    return $static_cache[$id];
  }
  else {
    $static_cache[$id] = array();
  }
  $importer = feeds_importer($id);
  $mappings = $importer->processor
    ->getMappings();
  foreach ($mappings as $mapping) {
    $static_cache[$id][] = $mapping['target'];
  }
  return $static_cache[$id];
}

/**
 * Callback for mapping.
 *
 * @param FeedsSource $source
 *   A FeedsSource object.
 * @param Entity $entity
 *   The entity to map to.
 * @param string $target
 *   The target key on $entity to map to.
 * @param array $value
 *   The value to map. Should be an array.
 */
function entityreference_feeds_feeds_set_target($source, $entity, $target, $value, $mapping) {
  $entity->entityreference_feeds_source_id = $source->id;

  //$entity->entityreference_feeds_source_feed_nid = $source->feed_nid;

  // Assume that the passed in value could really be any number of values.
  if (is_array($value)) {
    $values = $value;
  }
  else {
    $values = array(
      $value,
    );
  }
  list($field_name, $target_bundle, $target_callback, $target_target) = explode(':', $target, 4);

  // Get some useful field information.
  $field_info = field_info_field($field_name);
  $target_type = $field_info['settings']['target_type'];
  if (!isset($entity->entityreference_feeds_items)) {
    $entity->entityreference_feeds_items = array();
  }
  if (!isset($entity->entityreference_feeds_items[$field_name])) {
    $entity->entityreference_feeds_items[$field_name] = array();
  }
  if (!isset($entity->entityreference_feeds_items[$field_name][$target_bundle])) {
    $entity->entityreference_feeds_items[$field_name][$target_bundle] = array();
  }
  $bundle_items =& $entity->entityreference_feeds_items[$field_name][$target_bundle];
  foreach ($values as $delta => $target_value) {
    if (!isset($bundle_items[$delta])) {
      $target_entity_info = entity_get_info($target_type);
      $target_entity = new stdClass();
      if (!empty($target_entity_info['entity keys']['bundle'])) {
        $target_entity->{$target_entity_info['entity keys']['bundle']} = $target_bundle;
      }
      $bundle_items[$delta]['target_entity'] = $target_entity;
      $bundle_items[$delta]['creation_values'] = array();
    }
    else {
      $target_entity =& $bundle_items[$delta]['target_entity'];
    }
    if (!isset($target_entity->feeds_item)) {
      entityreference_feeds_newItemInfo($target_entity, $target_type, $source->id, $source->feed_nid);
    }
    if ($target_callback !== 'entityreference_feeds' && function_exists($target_callback)) {

      //$real_target = isset($targets[$target]['real_target']) ? $targets[$target]['real_target'] : $target_target;

      //$bundle_items[$delta]['real_targets'][$real_target] = $real_target;

      //Note: Most feed mappers, at least the ones in Feeds > 7.x-2.0-alpha8, assume target value is an array
      $target_callback($source, $target_entity, $target_target, array(
        $target_value,
      ), $mapping);
    }
    else {
      switch ($target_target) {
        case 'url':
        case 'guid':
          $target_entity->feeds_item->{$target_target} = $target_value;
          break;
        default:
          $bundle_items[$delta]['creation_values'][$target_target] = $target_value;
          break;
      }
    }
  }
}

/**
 * Helper function for creating entityreference_feeds entities.
 *
 * @param string $entity_type
 *   The entity type of the entity to be created.
 * @param string $bundle
 *   (optional) Name of the bundle of the entity to be created.
 * @param array $values
 *   (optional) An array of values as described by the entity's property info. All entity
 *   properties of the given entity type that are marked as required, must be
 *   present.
 *   If the passed values have no matching property, their value will be
 *   assigned to the entity directly, without the use of the metadata-wrapper
 *   property.
 *
 * @return Entity
 *   The created entity or FALSE if the entity could not be created.
 */
function _entityreference_feeds_create_entity($entity_type, $bundle = FALSE, $values = array()) {
  static $vocabularies = array();
  $info = entity_get_info($entity_type);
  $default_values = variable_get('entityreference_feeds_entity_default_values', array());
  $creation_values = $values + (isset($default_values[$entity_type]['values']) ? array_filter($default_values[$entity_type]['values']) : array());
  if (!empty($info['entity keys']['bundle']) && $bundle) {
    $creation_values += array(
      $info['entity keys']['bundle'] => $bundle,
    );
    if ($entity_type === 'taxonomy_term') {

      // For taxonomy term, add 'vid' property.
      if (!isset($vocabularies[$bundle])) {
        $vocabulary = taxonomy_vocabulary_machine_name_load($bundle);
        $vocabularies[$bundle] = $vocabulary ? $vocabulary->vid : 0;
      }
      $creation_values['vid'] = $vocabularies[$bundle];
    }
  }
  $entity_wrapper = entity_property_values_create_entity($entity_type, $creation_values);
  return $entity_wrapper ? $entity_wrapper
    ->value() : FALSE;
}

// This function is from FeedsProcessor.inc.

/**
 * Adds Feeds specific information on $entity->feeds_item.
 *
 * @param Entity $entity
 *   The entity object to be populated with new item info.
 * @param string $entity_type
 *   The type of the entity, for example 'node'.
 * @param int $feed_nid
 *   (optional) The feed nid of the source that produces this entity.
 * @param string $hash
 *   (optional) The fingerprint of the source item.
 */

//TODO: To use or not to use source_id and feed_nid?
function entityreference_feeds_newItemInfo($entity, $entity_type, $id, $feed_nid = 0, $hash = '') {
  $entity->feeds_item = new stdClass();
  $entity->feeds_item->entity_id = 0;
  $entity->feeds_item->entity_type = $entity_type;
  $entity->feeds_item->id = 'entityreference_feeds';
  $entity->feeds_item->feed_nid = $feed_nid;
  $entity->feeds_item->imported = REQUEST_TIME;
  $entity->feeds_item->hash = $hash;
  $entity->feeds_item->url = NULL;
  $entity->feeds_item->guid = NULL;
}

/**
 * Implements hook_entity_presave().
 */
function entityreference_feeds_entity_presave($entity, $type) {

  // @todo: job queue integration for async batching?
  static $property_info = array();
  if (isset($entity->entityreference_feeds_items)) {
    $mapped_targets = entityreference_feeds_processor_mapped_targets($entity->entityreference_feeds_source_id);
    foreach ($entity->entityreference_feeds_items as $field_name => $target_bundles) {

      // Get some useful field information.
      $info = field_info_field($field_name);
      $target_type = $info['settings']['target_type'];
      $field = array();
      foreach ($target_bundles as $target_bundle => $values) {

        //Used for metadata retrieval only
        if (!isset($property_info[$target_type][$target_bundle])) {
          if (!isset($property_info[$target_type])) {
            $property_info[$target_type] = array();
          }
          $metadata_wrapper = entity_metadata_wrapper($target_type, NULL, array(
            'bundle' => $target_bundle,
          ));
          $property_info[$target_type][$target_bundle] = $metadata_wrapper
            ->getPropertyInfo();
        }
        $targets = _entityreference_feeds_get_targets($field_name, $type, $target_bundle);
        foreach ($values as $value) {

          //TODO: This variable name is confusing! $partial_target_entity?
          $item = $value['target_entity'];

          // @todo load multiple?
          // @todo support other unique fields?
          // GUID is a requirement, else we will keep adding new entities on every update.
          if (!empty($item->feeds_item->guid)) {

            // Set aside feeds_item.
            $feeds_item = $item->feeds_item;
            unset($item->feeds_item);

            // Fetch the entity ID resulting from the mapping table look-up.
            // Clone query?
            $entity_id = db_query('SELECT entity_id FROM {feeds_item} WHERE guid = :guid', array(
              ':guid' => $feeds_item->guid,
            ))
              ->fetchField();

            // @todo try catch?
            if ($entity_id) {
              $target_entity = entity_load_single($target_type, $entity_id);
              foreach ($mapped_targets as $target) {
                if (isset($targets[$target])) {
                  $real_target = NULL;
                  if (isset($targets[$target]['real_target'])) {

                    //TODO: How is this used?
                    $real_target = $targets[$target]['real_target'];
                  }
                  else {
                    list($real_target) = explode(':', $target, 2);
                  }
                  if (isset($property_info[$target_type][$target_bundle][$real_target]) && isset($property_info[$target_type][$target_bundle][$real_target]['field']) && $property_info[$target_type][$target_bundle][$real_target]['field']) {

                    // is a field
                    $target_entity->{$real_target} = array(
                      'und' => array(),
                    );
                  }
                  else {
                    unset($target_entity->{$real_target});
                  }
                }
              }
              $wrapper = entity_metadata_wrapper($target_type, $target_entity);
              foreach ($value['creation_values'] as $target_element => $value) {
                $wrapper->{$target_element}
                  ->set($value);
              }
            }
            else {
              $target_entity = _entityreference_feeds_create_entity($target_type, $target_bundle, $value['creation_values']);
            }
            foreach (get_object_vars($item) as $target_element => $target_value) {
              $target_entity->{$target_element} = $target_value;
            }
            entity_save($target_type, $target_entity);
            list($entity_id) = entity_extract_ids($target_type, $target_entity);

            // Restore and save feeds_item.
            $target_entity->feeds_item = $feeds_item;
            feeds_item_info_save($target_entity, $entity_id);

            // Assign the target type and the target ID.
            $field_item = array(
              'target_type' => $target_type,
              'target_id' => $entity_id,
            );

            // Set the language of the field depending on entity language.
            $language = isset($item->language) ? $item->language : LANGUAGE_NONE;
            if (!isset($field[$language])) {
              $field[$language] = array();
            }
            $field[$language][] = $field_item;
          }
        }
      }
      $entity->{$field_name} = $field;
    }
  }
}

/**
 * Implements hook_menu().
 */
function entityreference_feeds_menu() {
  $items = array();
  $items['admin/config/content/entityreference_feeds'] = array(
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'entityreference_feeds_settings_form',
    ),
    'title' => 'Entityreference feeds',
    'description' => 'Configure Entityreference feeds settings',
    'access arguments' => array(
      'administer site configuration',
    ),
    'type' => MENU_NORMAL_ITEM,
  );
  return $items;
}

/**
 * Settings form callback.
 */
function entityreference_feeds_settings_form() {
  $info = entity_get_info();
  $default_values = variable_get('entityreference_feeds_entity_default_values', array());
  $form['entityreference_feeds_entity_default_values'] = array(
    '#title' => t('Default values'),
    '#type' => 'fieldset',
    '#tree' => TRUE,
  );

  //TODO: check restrictions on which entities can be references in entity reference field to avoid including weird stuff like rules_config etc
  foreach ($info as $entity_type => $entity_type_info) {
    if (entity_type_supports($entity_type, 'create')) {
      $wrapper = entity_metadata_wrapper($entity_type);
      if (!is_object($wrapper)) {
        continue;
      }
      $form['entityreference_feeds_entity_default_values'][$entity_type] = array(
        '#title' => $entity_type,
        '#type' => 'fieldset',
      );
      $entity_type_settings =& $form['entityreference_feeds_entity_default_values'][$entity_type];
      global $user;
      $formats = filter_formats($user);
      foreach ($formats as $format) {
        $format_options[$format->format] = check_plain($format->name);
      }
      $entity_type_settings['input_format'] = array(
        '#type' => 'select',
        '#title' => t('Text format'),
        '#description' => t('Select the input format for the body field of the nodes to be created.'),
        '#options' => $format_options,
        '#default_value' => isset($default_values[$entity_type]['input_format']) ? $default_values[$entity_type]['input_format'] : 'plain_text',
      );
      $bundle_key = $entity_type_info['entity keys']['bundle'];
      foreach ($wrapper
        ->getPropertyInfo() as $name => $property) {
        if (!empty($property['required']) && $name != $bundle_key) {
          $entity_type_settings['values'][$name] = array(
            '#type' => 'textfield',
            '#title' => $property['label'],
            '#description' => isset($property['description']) ? $property['description'] : '',
            '#default_value' => isset($default_values[$entity_type]['values'][$name]) ? $default_values[$entity_type]['values'][$name] : NULL,
          );
          if (!empty($property['options list'])) {
            $entity_type_settings['values'][$name]['#type'] = 'select';
            $entity_type_settings['values'][$name]['#options'] = $wrapper->{$name}
              ->optionsList();
          }

          // @todo: Maybe implement per data-type forms like Rules does?
          $entity_type_settings['values'][$name]['#description'] .= ' ' . t('Expected data type: %type.', array(
            '%type' => $wrapper->{$name}
              ->type(),
          ));
          if ($wrapper->{$name} instanceof EntityDrupalWrapper) {
            $info = $wrapper->{$name}
              ->entityInfo();
            if (!empty($info) && isset($info['entity keys']['id'])) {
              $id_info = $wrapper->{$name}
                ->get($info['entity keys']['id'])
                ->info();
              $entity_type_settings['values'][$name]['#description'] .= ' ' . t('Just enter the identifier of the entity, i.e. %id', array(
                '%id' => $id_info['label'],
              ));
            }
          }
        }
      }
    }
  }
  return system_settings_form($form);
}