Newer
Older
Alex Barth
committed
<?php
/**
* @file
* Contains FeedsProcessor and related classes.
*/
fietserwin
committed
// Insert mode for new items.
define('FEEDS_SKIP_NEW', 0);
define('FEEDS_INSERT_NEW', 1);
// Update mode for existing items.
define('FEEDS_SKIP_EXISTING', 0);
define('FEEDS_REPLACE_EXISTING', 1);
define('FEEDS_UPDATE_EXISTING', 2);
// Options for handling content in Drupal but not in source data.
define('FEEDS_SKIP_NON_EXISTENT', 'skip');
define('FEEDS_DELETE_NON_EXISTENT', 'delete');
// Default limit for creating items on a page load, not respected by all
// processors.
define('FEEDS_PROCESS_LIMIT', 50);
/**
* Thrown if a validation fails.
*/
class FeedsValidationException extends Exception {}
/**
* Thrown if a an access check fails.
*/
class FeedsAccessException extends Exception {}
Alex Barth
committed
/**
* Abstract class, defines interface for processors.
*/
abstract class FeedsProcessor extends FeedsPlugin {
Chris Leppanen
committed
/**
* Implements FeedsPlugin::pluginType().
*/
Chris Leppanen
committed
return 'processor';
}
/**
* @defgroup entity_api_wrapper Entity API wrapper.
*/
/**
* Entity type this processor operates on.
*/
public abstract function entityType();
/**
* Bundle type this processor operates on.
*
* Defaults to the entity type for entities that do not define bundles.
*
* @return string|NULL
* The bundle type this processor operates on, or NULL if it is undefined.
*/
public function bundle() {
return $this->config['bundle'];
}
/**
* Provides a list of bundle options for use in select lists.
*
* @return array
* A keyed array of bundle => label.
*/
public function bundleOptions() {
$options = array();
foreach (field_info_bundles($this->entityType()) as $bundle => $info) {
if (!empty($info['label'])) {
$options[$bundle] = $info['label'];
}
else {
$options[$bundle] = $bundle;
}
}
return $options;
}
/**
* Create a new entity.
*
MegaChriz
committed
* @param FeedsSource $source
* The feeds source that spawns this entity.
*
MegaChriz
committed
* @return object
* A new entity object.
*/
MegaChriz
committed
protected function newEntity(FeedsSource $source) {
$entity = new stdClass();
$info = $this->entityInfo();
if (!empty($info['entity keys']['language'])) {
$entity->{$info['entity keys']['language']} = $this->entityLanguage();
}
return $entity;
}
/**
* Load an existing entity.
*
* @param $source
* The feeds source that spawns this entity.
* @param $entity_id
* The unique id of the entity that should be loaded.
*
* @return
* A new entity object.
*
* @todo We should be able to batch load these, if we found all of the
* existing ids first.
protected function entityLoad(FeedsSource $source, $entity_id) {
MegaChriz
committed
$info = $this->entityInfo();
if ($this->config['update_existing'] == FEEDS_UPDATE_EXISTING) {
$entities = entity_load($this->entityType(), array($entity_id));
MegaChriz
committed
$entity = reset($entities);
}
else {
$args = array(':entity_id' => $entity_id);
$table = db_escape_table($info['base table']);
$key = db_escape_field($info['entity keys']['id']);
$entity = db_query("SELECT * FROM {" . $table . "} WHERE $key = :entity_id", $args)->fetchObject();
MegaChriz
committed
if ($entity && !empty($info['entity keys']['language'])) {
$entity->{$info['entity keys']['language']} = $this->entityLanguage();
}
MegaChriz
committed
return $entity;
MegaChriz
committed
* Validates an entity.
*
* @throws FeedsValidationException $e
MegaChriz
committed
* Thrown if validation fails.
MegaChriz
committed
protected function entityValidate($entity) {
$info = $this->entityInfo();
if (empty($info['entity keys']['language'])) {
return;
}
// Ensure that a valid language is always set.
$key = $info['entity keys']['language'];
$languages = language_list('enabled');
if (empty($entity->$key) || !isset($languages[1][$entity->$key])) {
$entity->$key = $this->entityLanguage();
}
}
/**
* Access check for saving an enity.
*
* @param $entity
* Entity to be saved.
*
* @throws FeedsAccessException $e
* If the access check fails.
*/
protected function entitySaveAccess($entity) {}
/**
* Save an entity.
*
* @param $entity
*/
protected abstract function entitySave($entity);
/**
* Delete a series of entities.
*
* @param $entity_ids
* Array of unique identity ids to be deleted.
*/
protected abstract function entityDeleteMultiple($entity_ids);
/**
* Wrap entity_get_info() into a method so that extending classes can override
* it and more entity information. Allowed additional keys:
*
* 'label plural' ... the plural label of an entity type.
*/
protected function entityInfo() {
megachriz
committed
$info = entity_get_info($this->entityType());
// Entity module has defined the plural label in "plural label" instead of
// "label plural". So if "plural label" is defined, this will have priority
// over "label plural".
if (isset($info['plural label'])) {
$info['label plural'] = $info['plural label'];
}
return $info;
MegaChriz
committed
/**
* Returns the current language for entities.
*
* This checks if the configuration value is valid.
*
* @return string
* The current language code.
*/
protected function entityLanguage() {
if (!module_exists('locale')) {
// language_list() may return languages even if the locale module is
// disabled. See https://www.drupal.org/node/173227 why.
// When the locale module is disabled, there are no selectable languages
// in the UI, so the content should be imported in LANGUAGE_NONE.
return LANGUAGE_NONE;
}
$languages = language_list('enabled');
return isset($languages[1][$this->config['language']]) ? $this->config['language'] : LANGUAGE_NONE;
}
/**
* @}
*/
Alex Barth
committed
/**
Alex Barth
committed
*
* @param FeedsSource $source
* Source information about this import.
* @param FeedsParserResult $parser_result
* The result of the parsing stage.
Alex Barth
committed
*/
public function process(FeedsSource $source, FeedsParserResult $parser_result) {
$state = $source->state(FEEDS_PROCESS);
if (!isset($state->removeList) && $parser_result->items) {
$this->initEntitiesToBeRemoved($source, $state);
}
fietserwin
committed
$skip_new = $this->config['insert_new'] == FEEDS_SKIP_NEW;
$skip_existing = $this->config['update_existing'] == FEEDS_SKIP_EXISTING;
while ($item = $parser_result->shiftItem()) {
// Check if this item already exists.
$entity_id = $this->existingEntityId($source, $parser_result);
// If it's included in the feed, it must not be removed on clean.
if ($entity_id) {
unset($state->removeList[$entity_id]);
}
module_invoke_all('feeds_before_update', $source, $item, $entity_id);
fietserwin
committed
// If it exists, and we are not updating, or if it does not exist, and we
// are not inserting, pass onto the next item.
if (($entity_id && $skip_existing) || (!$entity_id && $skip_new)) {
continue;
}
try {
$hash = $this->hash($item);
fietserwin
committed
$changed = $hash !== $this->getHash($entity_id);
// Do not proceed if the item exists, has not changed, and we're not
// forcing the update.
fietserwin
committed
if ($entity_id && !$changed && !$this->config['skip_hash_check']) {
continue;
}
// Load an existing entity.
$entity = $this->entityLoad($source, $entity_id);
// The feeds_item table is always updated with the info for the most
// recently processed entity. The only carryover is the entity_id.
$this->newItemInfo($entity, $source->feed_nid, $hash);
$entity->feeds_item->entity_id = $entity_id;
$entity->feeds_item->is_new = FALSE;
// Build a new entity.
else {
$entity = $this->newEntity($source);
$this->newItemInfo($entity, $source->feed_nid, $hash);
// Set property and field values.
$this->map($source, $parser_result, $entity);
$this->entityValidate($entity);
// Allow modules to alter the entity before saving.
module_invoke_all('feeds_presave', $source, $entity, $item, $entity_id);
if (module_exists('rules')) {
rules_invoke_event('feeds_import_'. $source->importer()->id, $entity);
}
// Enable modules to skip saving at all.
if (!empty($entity->feeds_item->skip)) {
continue;
}
// This will throw an exception on failure.
$this->entitySaveAccess($entity);
$this->entitySave($entity);
// Allow modules to perform operations using the saved entity data.
// $entity contains the updated entity after saving.
module_invoke_all('feeds_after_save', $source, $entity, $item, $entity_id);
// Track progress.
if (empty($entity_id)) {
$state->created++;
else {
$state->updated++;
// Something bad happened, log it.
catch (Exception $e) {
$state->failed++;
drupal_set_message($e->getMessage(), 'warning');
list($message, $arguments) = $this->createLogEntry($e, $entity, $item);
$source->log('import', $message, $arguments, WATCHDOG_ERROR);
}
// Set messages if we're done.
if ($source->progressImporting() != FEEDS_BATCH_COMPLETE) {
return;
}
// Remove not included items if needed.
// It depends on the implementation of the clean() method what will happen
// to items that were no longer in the source.
$this->clean($state);
$info = $this->entityInfo();
$tokens = array(
'@entity' => strtolower($info['label']),
'@entities' => strtolower($info['label plural']),
);
$messages = array();
if ($state->created) {
$messages[] = array(
'message' => format_plural(
$state->created,
'Created @number @entity.',
'Created @number @entities.',
array('@number' => $state->created) + $tokens
),
);
}
if ($state->updated) {
$messages[] = array(
'message' => format_plural(
$state->updated,
'Updated @number @entity.',
'Updated @number @entities.',
array('@number' => $state->updated) + $tokens
),
if ($state->unpublished) {
$messages[] = array(
'message' => format_plural(
mikran
committed
$state->unpublished,
'Unpublished @number @entity.',
'Unpublished @number @entities.',
array('@number' => $state->unpublished) + $tokens
),
);
}
if ($state->blocked) {
$messages[] = array(
'message' => format_plural(
$state->blocked,
'Blocked @number @entity.',
'Blocked @number @entities.',
array('@number' => $state->blocked) + $tokens
),
);
}
if ($state->deleted) {
$messages[] = array(
'message' => format_plural(
$state->deleted,
'Removed @number @entity.',
'Removed @number @entities.',
array('@number' => $state->deleted) + $tokens
),
);
}
if ($state->failed) {
$messages[] = array(
'message' => format_plural(
$state->failed,
'Failed importing @number @entity.',
'Failed importing @number @entities.',
array('@number' => $state->failed) + $tokens
),
'level' => WATCHDOG_ERROR,
);
}
if (empty($messages)) {
$messages[] = array(
'message' => t('There are no new @entities.', array('@entities' => strtolower($info['label plural']))),
);
}
foreach ($messages as $message) {
drupal_set_message($message['message']);
$source->log('import', $message['message'], array(), isset($message['level']) ? $message['level'] : WATCHDOG_INFO);
Alex Barth
committed
/**
* Initializes the list of entities to remove.
*
* This populates $state->removeList with all existing entities previously
* imported from the source.
*
* @param FeedsSource $source
* Source information about this import.
* @param FeedsState $state
* The FeedsState object for the given stage.
*/
protected function initEntitiesToBeRemoved(FeedsSource $source, FeedsState $state) {
$state->removeList = array();
// We fill it only if needed.
if ($this->config['update_non_existent'] == FEEDS_SKIP_NON_EXISTENT) {
return;
}
// Get the full list of entities for this source.
$entity_ids = db_select('feeds_item')
->fields('feeds_item', array('entity_id'))
->condition('entity_type', $this->entityType())
->condition('id', $this->id)
->condition('feed_nid', $source->feed_nid)
->condition('hash', $this->config['update_non_existent'], '<>')
->execute()
->fetchCol();
if (!$entity_ids) {
return;
}
$state->removeList = array_combine($entity_ids, $entity_ids);
}
/**
* Deletes entities which were not found during processing.
*
* @todo batch delete?
*
* @param FeedsState $state
* The FeedsState object for the given stage.
*/
protected function clean(FeedsState $state) {
// We clean only if needed.
megachriz
committed
if ($this->config['update_non_existent'] != FEEDS_DELETE_NON_EXISTENT) {
return;
}
if ($total = count($state->removeList)) {
$this->entityDeleteMultiple($state->removeList);
$state->deleted += $total;
}
}
Alex Barth
committed
/**
* Remove all stored results or stored results up to a certain time for a
* source.
Alex Barth
committed
*
* @param FeedsSource $source
* Source information for this expiry. Implementers should only delete items
* pertaining to this source. The preferred way of determining whether an
* item pertains to a certain souce is by using $source->feed_nid. It is the
* processor's responsibility to store the feed_nid of an imported item in
* the processing stage.
*/
public function clear(FeedsSource $source) {
$state = $source->state(FEEDS_PROCESS_CLEAR);
// Build base select statement.
$select = db_select('feeds_item')
->fields('feeds_item', array('entity_id'))
->condition('entity_type', $this->entityType())
->condition('id', $this->id)
->condition('feed_nid', $source->feed_nid);
// If there is no total, query it.
if (!$state->total) {
$state->total = $select->countQuery()->execute()->fetchField();
}
// Delete a batch of entities.
$entity_ids = $select->range(0, $this->getLimit())->execute()->fetchCol();
$this->entityDeleteMultiple($entity_ids);
// Report progress, take into account that we may not have deleted as many
// items as we have counted at first.
if ($deleted_count = count($entity_ids)) {
$state->deleted += $deleted_count;
$state->progress($state->total, $state->deleted);
}
else {
$state->progress($state->total, $state->total);
}
// Report results when done.
if ($source->progressClearing() == FEEDS_BATCH_COMPLETE) {
$info = $this->entityInfo();
if ($state->deleted) {
$message = format_plural(
$state->deleted,
'Deleted @number @entity',
'Deleted @number @entities',
array(
'@number' => $state->deleted,
'@entity' => strtolower($info['label']),
'@entities' => strtolower($info['label plural']),
)
);
$source->log('clear', $message, array(), WATCHDOG_INFO);
drupal_set_message($message);
}
else {
drupal_set_message(t('There are no @entities to be deleted.', array('@entities' => $info['label plural'])));
}
}
}
/*
* Report number of items that can be processed per call.
*
* 0 means 'unlimited'.
*
* If a number other than 0 is given, Feeds parsers that support batching
* will only deliver this limit to the processor.
*
* @see FeedsSource::getLimit()
* @see FeedsCSVParser::parse()
*/
public function getLimit() {
return variable_get('feeds_process_limit', FEEDS_PROCESS_LIMIT);
Alex Barth
committed
/**
* Deletes feed items older than REQUEST_TIME - $time.
Alex Barth
committed
*
* Do not invoke expire on a processor directly, but use
* FeedsSource::expire() instead.
*
* @param FeedsSource $source
* The source to expire entities for.
Alex Barth
committed
*
* @param $time
* (optional) All items produced by this configuration that are older than
* REQUEST_TIME - $time should be deleted. If NULL, processor should use
* internal configuration. Defaults to NULL.
* @return float
* FEEDS_BATCH_COMPLETE if all items have been processed, a float between 0
* and 0.99* indicating progress otherwise.
*
* @see FeedsSource::expire()
Alex Barth
committed
*/
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
public function expire(FeedsSource $source, $time = NULL) {
$state = $source->state(FEEDS_PROCESS_EXPIRE);
if ($time === NULL) {
$time = $this->expiryTime();
}
if ($time == FEEDS_EXPIRE_NEVER) {
return;
}
$select = $this->expiryQuery($source, $time);
// If there is no total, query it.
if (!$state->total) {
$state->total = $select->countQuery()->execute()->fetchField();
}
// Delete a batch of entities.
$entity_ids = $select->range(0, $this->getLimit())->execute()->fetchCol();
if ($entity_ids) {
$this->entityDeleteMultiple($entity_ids);
$state->deleted += count($entity_ids);
$state->progress($state->total, $state->deleted);
}
else {
$state->progress($state->total, $state->total);
}
}
/**
* Returns a database query used to select entities to expire.
*
* Processor classes should override this method to set the age portion of the
* query.
*
* @param FeedsSource $source
* The feed source.
* @param int $time
* Delete entities older than this.
*
* @return SelectQuery
* A select query to execute.
*
* @see FeedsNodeProcessor::expiryQuery()
*/
protected function expiryQuery(FeedsSource $source, $time) {
// Build base select statement.
$info = $this->entityInfo();
$id_key = $info['entity keys']['id'];
$select = db_select($info['base table'], 'e');
$select->addField('e', $id_key);
$select->join('feeds_item', 'fi', "e.$id_key = fi.entity_id");
$select->condition('fi.entity_type', $this->entityType());
$select->condition('fi.id', $this->id);
$select->condition('fi.feed_nid', $source->feed_nid);
return $select;
Alex Barth
committed
Alex Barth
committed
/**
* Counts the number of items imported by this processor.
Alex Barth
committed
*/
public function itemCount(FeedsSource $source) {
return db_query("SELECT count(*) FROM {feeds_item} WHERE id = :id AND entity_type = :entity_type AND feed_nid = :feed_nid", array(':id' => $this->id, ':entity_type' => $this->entityType(), ':feed_nid' => $source->feed_nid))->fetchField();
Alex Barth
committed
}
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
/**
* Returns a statically cached version of the target mappings.
*
* @return array
* The targets for this importer.
*/
protected function getCachedTargets() {
$targets = &drupal_static('FeedsProcessor::getCachedTargets', array());
if (!isset($targets[$this->id])) {
$targets[$this->id] = $this->getMappingTargets();
}
return $targets[$this->id];
}
/**
* Returns a statically cached version of the source mappings.
*
* @return array
* The sources for this importer.
*/
protected function getCachedSources() {
$sources = &drupal_static('FeedsProcessor::getCachedSources', array());
if (!isset($sources[$this->id])) {
$sources[$this->id] = feeds_importer($this->id)->parser->getMappingSources();
if (is_array($sources[$this->id])) {
foreach ($sources[$this->id] as $source_key => $source) {
if (empty($source['callback']) || !is_callable($source['callback'])) {
unset($sources[$this->id][$source_key]['callback']);
}
}
}
}
return $sources[$this->id];
}
Alex Barth
committed
/**
* Execute mapping on an item.
*
* This method encapsulates the central mapping functionality. When an item is
* processed, it is passed through map() where the properties of $source_item
* are mapped onto $target_item following the processor's mapping
* configuration.
*
* For each mapping FeedsParser::getSourceElement() is executed to retrieve
* the source element, then FeedsProcessor::setTargetElement() is invoked
* to populate the target item properly. Alternatively a
* hook_x_targets_alter() may have specified a callback for a mapping target
* in which case the callback is asked to populate the target item instead of
* FeedsProcessor::setTargetElement().
*
* @ingroup mappingapi
*
* @see hook_feeds_processor_targets()
* @see hook_feeds_processor_targets_alter()
Alex Barth
committed
*/
protected function map(FeedsSource $source, FeedsParserResult $result, $target_item = NULL) {
$targets = $this->getCachedTargets();
// Get fields for the entity type we are mapping to.
$fields = field_info_instances($this->entityType(), $this->bundle());
Alex Barth
committed
if (empty($target_item)) {
$target_item = array();
}
// Many mappers add to existing fields rather than replacing them. Hence we
// need to clear target elements of each item before mapping in case we are
// mapping on a prepopulated item such as an existing node.
foreach ($this->getMappings() as $mapping) {
if (isset($targets[$mapping['target']]['real_target'])) {
$target_name = $targets[$mapping['target']]['real_target'];
}
Chris Leppanen
committed
else {
$target_name = $mapping['target'];
}
// If the target is a field empty the value for the targeted language
// only.
// In all other cases, just empty the target completely.
if (isset($fields[$target_name])) {
// Empty the target for the specified language.
$target_item->{$target_name}[$mapping['language']] = array();
}
else {
// Empty the whole target.
$target_item->{$target_name} = NULL;
}
}
// This is where the actual mapping happens: For every mapping we invoke
// the parser's getSourceElement() method to retrieve the value of the
// source element and pass it to the processor's setTargetElement() to stick
// it on the right place of the target item.
foreach ($this->getMappings() as $mapping) {
$value = $this->getSourceValue($source, $result, $mapping['source']);
$this->mapToTarget($source, $mapping['target'], $target_item, $value, $mapping);
}
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
return $target_item;
}
/**
* Returns the values from the parser, or callback.
*
* @param FeedsSource $source
* The feed source.
* @param FeedsParserResult $result
* The parser result.
* @param string $source_key
* The current key being processed.
*
* @return mixed
* A value, or a list of values.
*/
protected function getSourceValue(FeedsSource $source, FeedsParserResult $result, $source_key) {
$sources = $this->getCachedSources();
if (isset($sources[$source_key]['callback'])) {
return call_user_func($sources[$source_key]['callback'], $source, $result, $source_key);
}
return feeds_importer($this->id)->parser->getSourceElement($source, $result, $source_key);
}
/**
* Maps values onto the target item.
*
* @param FeedsSource $source
* The feed source.
* @param mixed &$target_item
* The target item to apply values into.
* @param mixed $value
* A value, or a list of values.
* @param array $mapping
* The mapping configuration.
*/
protected function mapToTarget(FeedsSource $source, $target, &$target_item, $value, array $mapping) {
$targets = $this->getCachedTargets();
// Map the source element's value to the target.
// If the mapping specifies a callback method, use the callback instead of
// setTargetElement().
if (isset($targets[$target]['callback'])) {
// All target callbacks expect an array.
if (!is_array($value)) {
$value = array($value);
}
call_user_func($targets[$target]['callback'], $source, $target_item, $target, $value, $mapping);
Alex Barth
committed
}
else {
$this->setTargetElement($source, $target_item, $target, $value, $mapping);
}
Alex Barth
committed
}
/**
* Per default, don't support expiry. If processor supports expiry of imported
* items, return the time after which items should be removed.
*/
public function expiryTime() {
return FEEDS_EXPIRE_NEVER;
}
/**
* Declare default configuration.
*/
public function configDefaults() {
$info = $this->entityInfo();
$bundle = NULL;
if (empty($info['entity keys']['bundle'])) {
$bundle = $this->entityType();
}
return array(
'mappings' => array(),
fietserwin
committed
'insert_new' => FEEDS_INSERT_NEW,
'update_existing' => FEEDS_SKIP_EXISTING,
'update_non_existent' => FEEDS_SKIP_NON_EXISTENT,
Chris Leppanen
committed
'input_format' => NULL,
'skip_hash_check' => FALSE,
'bundle' => $bundle,
MegaChriz
committed
'language' => LANGUAGE_NONE,
);
}
/**
* Overrides parent::configForm().
*/
public function configForm(&$form_state) {
$info = $this->entityInfo();
$form = array();
if (!empty($info['entity keys']['bundle'])) {
$form['bundle'] = array(
'#type' => 'select',
'#options' => $this->bundleOptions(),
Chris Leppanen
committed
'#title' => !empty($info['bundle name']) ? $info['bundle name'] : t('Bundle'),
'#required' => TRUE,
'#default_value' => $this->bundle(),
);
}
else {
$form['bundle'] = array(
'#type' => 'value',
'#value' => $this->entityType(),
);
}
MegaChriz
committed
if (module_exists('locale') && !empty($info['entity keys']['language'])) {
$form['language'] = array(
'#type' => 'select',
'#options' => array(LANGUAGE_NONE => t('Language neutral')) + locale_language_list('name'),
'#title' => t('Language'),
'#required' => TRUE,
'#default_value' => $this->config['language'],
);
}
$tokens = array('@entities' => strtolower($info['label plural']));
fietserwin
committed
$form['insert_new'] = array(
'#type' => 'radios',
'#title' => t('Insert new @entities', $tokens),
'#description' => t('New @entities will be determined using mappings that are a "unique target".', $tokens),
'#options' => array(
FEEDS_INSERT_NEW => t('Insert new @entities', $tokens),
FEEDS_SKIP_NEW => t('Do not insert new @entities', $tokens),
),
'#default_value' => $this->config['insert_new'],
);
$form['update_existing'] = array(
'#type' => 'radios',
'#title' => t('Update existing @entities', $tokens),
'#description' => t('Existing @entities will be determined using mappings that are a "unique target".', $tokens),
'#options' => array(
FEEDS_SKIP_EXISTING => t('Do not update existing @entities', $tokens),
FEEDS_REPLACE_EXISTING => t('Replace existing @entities', $tokens),
FEEDS_UPDATE_EXISTING => t('Update existing @entities', $tokens),
),
'#default_value' => $this->config['update_existing'],
'#after_build' => array('feeds_processor_config_form_update_existing_after_build'),
'#tokens' => $tokens,
global $user;
$formats = filter_formats($user);
foreach ($formats as $format) {
$format_options[$format->format] = $format->name;
Chris Leppanen
committed
$form['skip_hash_check'] = array(
'#type' => 'checkbox',
'#title' => t('Skip hash check'),
'#description' => t('Force update of items even if item source data did not change.'),
'#default_value' => $this->config['skip_hash_check'],
);
$form['input_format'] = array(
'#type' => 'select',
'#title' => t('Text format'),
'#description' => t('Select the default input format for the text fields of the nodes to be created.'),
'#options' => $format_options,
'#default_value' => isset($this->config['input_format']) ? $this->config['input_format'] : 'plain_text',
'#required' => TRUE,
);
$form['update_non_existent'] = array(
'#type' => 'radios',
'#title' => t('Action to take when previously imported @entities are missing in the feed', $tokens),
'#description' => t('Select how @entities previously imported and now missing in the feed should be updated.', $tokens),
'#options' => array(
FEEDS_SKIP_NON_EXISTENT => t('Skip non-existent @entities', $tokens),
FEEDS_DELETE_NON_EXISTENT => t('Delete non-existent @entities', $tokens),
),
'#default_value' => $this->config['update_non_existent'],
);
Alex Barth
committed
}
/**
* Get mappings.
*/
public function getMappings() {
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
$cache = &drupal_static('FeedsProcessor::getMappings', array());
if (!isset($cache[$this->id])) {
$mappings = $this->config['mappings'];
$targets = $this->getCachedTargets();
$languages = language_list('enabled');
foreach ($mappings as &$mapping) {
if (isset($targets[$mapping['target']]['preprocess_callbacks'])) {
foreach ($targets[$mapping['target']]['preprocess_callbacks'] as $callback) {
call_user_func_array($callback, array($targets[$mapping['target']], &$mapping));
}
}
// Ensure there's always a language set.
if (empty($mapping['language'])) {
$mapping['language'] = LANGUAGE_NONE;
}
else {
// Check if the configured language is available. If not, fallback to
// LANGUAGE_NONE.
if (!isset($languages[1][$mapping['language']])) {
$mapping['language'] = LANGUAGE_NONE;
}
}
}
$cache[$this->id] = $mappings;
}
return $cache[$this->id];
Alex Barth
committed
}
/**
* Declare possible mapping targets that this processor exposes.
*
* @ingroup mappingapi
Alex Barth
committed
*
* @return
* An array of mapping targets. Keys are paths to targets
* separated by ->, values are TRUE if target can be unique,
* FALSE otherwise.
*/
public function getMappingTargets() {
Chris Leppanen
committed
// The bundle has not been selected.
if (!$this->bundle()) {
$info = $this->entityInfo();
$bundle_name = !empty($info['bundle name']) ? drupal_strtolower($info['bundle name']) : t('bundle');
$plugin_key = feeds_importer($this->id)->config['processor']['plugin_key'];
$url = url('admin/structure/feeds/' . $this->id . '/settings/' . $plugin_key);
drupal_set_message(t('Please <a href="@url">select a @bundle_name</a>.', array('@url' => $url, '@bundle_name' => $bundle_name)), 'warning', FALSE);
}
return array(
'url' => array(
'name' => t('URL'),
'description' => t('The external URL of the item. E. g. the feed item URL in the case of a syndication feed. May be unique.'),
'optional_unique' => TRUE,
),
'guid' => array(
'name' => t('GUID'),
'description' => t('The globally unique identifier of the item. E. g. the feed item GUID in the case of a syndication feed. May be unique.'),
'optional_unique' => TRUE,
),
);