first import
This commit is contained in:
291
sites/all/modules/feeds/includes/FeedsConfigurable.inc
Normal file
291
sites/all/modules/feeds/includes/FeedsConfigurable.inc
Normal file
@@ -0,0 +1,291 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* FeedsConfigurable and helper functions.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Used when an object does not exist in the DB or code but should.
|
||||
*/
|
||||
class FeedsNotExistingException extends Exception {
|
||||
}
|
||||
|
||||
/**
|
||||
* Base class for configurable classes. Captures configuration handling, form
|
||||
* handling and distinguishes between in-memory configuration and persistent
|
||||
* configuration.
|
||||
*/
|
||||
abstract class FeedsConfigurable {
|
||||
|
||||
// Holds the actual configuration information.
|
||||
protected $config;
|
||||
|
||||
// A unique identifier for the configuration.
|
||||
protected $id;
|
||||
|
||||
/*
|
||||
CTools export type of this object.
|
||||
|
||||
@todo Should live in FeedsImporter. Not all child classes
|
||||
of FeedsConfigurable are exportable. Same goes for $disabled.
|
||||
|
||||
Export type can be one of
|
||||
FEEDS_EXPORT_NONE - the configurable only exists in memory
|
||||
EXPORT_IN_DATABASE - the configurable is defined in the database.
|
||||
EXPORT_IN_CODE - the configurable is defined in code.
|
||||
EXPORT_IN_CODE | EXPORT_IN_DATABASE - the configurable is defined in code, but
|
||||
overridden in the database.*/
|
||||
protected $export_type;
|
||||
|
||||
/**
|
||||
* CTools export enabled status of this object.
|
||||
*/
|
||||
protected $disabled;
|
||||
|
||||
/**
|
||||
* Instantiate a FeedsConfigurable object.
|
||||
*
|
||||
* Don't use directly, use feeds_importer() or feeds_plugin()
|
||||
* instead.
|
||||
*/
|
||||
public static function instance($class, $id) {
|
||||
// This is useful at least as long as we're developing.
|
||||
if (empty($id)) {
|
||||
throw new Exception(t('Empty configuration identifier.'));
|
||||
}
|
||||
static $instances = array();
|
||||
if (!isset($instances[$class][$id])) {
|
||||
$instances[$class][$id] = new $class($id);
|
||||
}
|
||||
return $instances[$class][$id];
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor, set id and load default configuration.
|
||||
*/
|
||||
protected function __construct($id) {
|
||||
// Set this object's id.
|
||||
$this->id = $id;
|
||||
// Per default we assume that a Feeds object is not saved to
|
||||
// database nor is it exported to code.
|
||||
$this->export_type = FEEDS_EXPORT_NONE;
|
||||
// Make sure configuration is populated.
|
||||
$this->config = $this->configDefaults();
|
||||
$this->disabled = FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Override magic method __isset(). This is needed due to overriding __get().
|
||||
*/
|
||||
public function __isset($name) {
|
||||
return isset($this->$name) ? TRUE : FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether this object is persistent and enabled. I. e. it is
|
||||
* defined either in code or in the database and it is enabled.
|
||||
*/
|
||||
public function existing() {
|
||||
if ($this->export_type == FEEDS_EXPORT_NONE) {
|
||||
throw new FeedsNotExistingException(t('Object is not persistent.'));
|
||||
}
|
||||
if ($this->disabled) {
|
||||
throw new FeedsNotExistingException(t('Object is disabled.'));
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save a configuration. Concrete extending classes must implement a save
|
||||
* operation.
|
||||
*/
|
||||
public abstract function save();
|
||||
|
||||
/**
|
||||
* Copy a configuration.
|
||||
*/
|
||||
public function copy(FeedsConfigurable $configurable) {
|
||||
$this->setConfig($configurable->config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set configuration.
|
||||
*
|
||||
* @param $config
|
||||
* Array containing configuration information. Config array will be filtered
|
||||
* by the keys returned by configDefaults() and populated with default
|
||||
* values that are not included in $config.
|
||||
*/
|
||||
public function setConfig($config) {
|
||||
$defaults = $this->configDefaults();
|
||||
$this->config = array_intersect_key($config, $defaults) + $defaults;
|
||||
}
|
||||
|
||||
/**
|
||||
* Similar to setConfig but adds to existing configuration.
|
||||
*
|
||||
* @param $config
|
||||
* Array containing configuration information. Will be filtered by the keys
|
||||
* returned by configDefaults().
|
||||
*/
|
||||
public function addConfig($config) {
|
||||
$this->config = is_array($this->config) ? array_merge($this->config, $config) : $config;
|
||||
$default_keys = $this->configDefaults();
|
||||
$this->config = array_intersect_key($this->config, $default_keys);
|
||||
}
|
||||
|
||||
/**
|
||||
* Override magic method __get(). Make sure that $this->config goes through
|
||||
* getConfig().
|
||||
*/
|
||||
public function __get($name) {
|
||||
if ($name == 'config') {
|
||||
return $this->getConfig();
|
||||
}
|
||||
return isset($this->$name) ? $this->$name : NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements getConfig().
|
||||
*
|
||||
* Return configuration array, ensure that all default values are present.
|
||||
*/
|
||||
public function getConfig() {
|
||||
$defaults = $this->configDefaults();
|
||||
return $this->config + $defaults;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return default configuration.
|
||||
*
|
||||
* @todo rename to getConfigDefaults().
|
||||
*
|
||||
* @return
|
||||
* Array where keys are the variable names of the configuration elements and
|
||||
* values are their default values.
|
||||
*/
|
||||
public function configDefaults() {
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return configuration form for this object. The keys of the configuration
|
||||
* form must match the keys of the array returned by configDefaults().
|
||||
*
|
||||
* @return
|
||||
* FormAPI style form definition.
|
||||
*/
|
||||
public function configForm(&$form_state) {
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Validation handler for configForm().
|
||||
*
|
||||
* Set errors with form_set_error().
|
||||
*
|
||||
* @param $values
|
||||
* An array that contains the values entered by the user through configForm.
|
||||
*/
|
||||
public function configFormValidate(&$values) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Submission handler for configForm().
|
||||
*
|
||||
* @param $values
|
||||
*/
|
||||
public function configFormSubmit(&$values) {
|
||||
$this->addConfig($values);
|
||||
$this->save();
|
||||
drupal_set_message(t('Your changes have been saved.'));
|
||||
feeds_cache_clear(FALSE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Config form wrapper. Use to render the configuration form of
|
||||
* a FeedsConfigurable object.
|
||||
*
|
||||
* @param $configurable
|
||||
* FeedsConfigurable object.
|
||||
* @param $form_method
|
||||
* The form method that should be rendered.
|
||||
*
|
||||
* @return
|
||||
* Config form array if available. NULL otherwise.
|
||||
*/
|
||||
function feeds_get_form($configurable, $form_method) {
|
||||
if (method_exists($configurable, $form_method)) {
|
||||
return drupal_get_form(get_class($configurable) . '_feeds_form', $configurable, $form_method);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Config form callback. Don't call directly, but use
|
||||
* feeds_get_form($configurable, 'method') instead.
|
||||
*
|
||||
* @param
|
||||
* FormAPI $form_state.
|
||||
* @param
|
||||
* FeedsConfigurable object.
|
||||
* @param
|
||||
* The object to perform the save() operation on.
|
||||
* @param $form_method
|
||||
* The $form_method that should be rendered.
|
||||
*/
|
||||
function feeds_form($form, &$form_state, $configurable, $form_method) {
|
||||
$form = $configurable->$form_method($form_state);
|
||||
$form['#configurable'] = $configurable;
|
||||
$form['#feeds_form_method'] = $form_method;
|
||||
$form['#validate'] = array('feeds_form_validate');
|
||||
$form['#submit'] = array('feeds_form_submit');
|
||||
$form['submit'] = array(
|
||||
'#type' => 'submit',
|
||||
'#value' => t('Save'),
|
||||
'#weight' => 100,
|
||||
);
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validation handler for feeds_form().
|
||||
*/
|
||||
function feeds_form_validate($form, &$form_state) {
|
||||
_feeds_form_helper($form, $form_state, 'Validate');
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit handler for feeds_form().
|
||||
*/
|
||||
function feeds_form_submit($form, &$form_state) {
|
||||
_feeds_form_helper($form, $form_state, 'Submit');
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper for Feeds validate and submit callbacks.
|
||||
*/
|
||||
function _feeds_form_helper($form, &$form_state, $action) {
|
||||
$method = $form['#feeds_form_method'] . $action;
|
||||
$class = get_class($form['#configurable']);
|
||||
$id = $form['#configurable']->id;
|
||||
|
||||
// Re-initialize the configurable object. Using feeds_importer() and
|
||||
// feeds_plugin() will ensure that we're using the same instance. We can't
|
||||
// reuse the previous form instance because feeds_importer() is used to save.
|
||||
// This will re-initialize all of the plugins anyway, causing some tricky
|
||||
// saving issues in certain cases.
|
||||
// See http://drupal.org/node/1672880.
|
||||
|
||||
if ($class == variable_get('feeds_importer_class', 'FeedsImporter')) {
|
||||
$form['#configurable'] = feeds_importer($id);
|
||||
}
|
||||
else {
|
||||
$form['#configurable'] = feeds_plugin($class, $id);
|
||||
}
|
||||
|
||||
if (method_exists($form['#configurable'], $method)) {
|
||||
$form['#configurable']->$method($form_state['values']);
|
||||
}
|
||||
}
|
333
sites/all/modules/feeds/includes/FeedsImporter.inc
Normal file
333
sites/all/modules/feeds/includes/FeedsImporter.inc
Normal file
@@ -0,0 +1,333 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* FeedsImporter class and related.
|
||||
*/
|
||||
|
||||
/**
|
||||
* A FeedsImporter object describes how an external source should be fetched,
|
||||
* parsed and processed. Feeds can manage an arbitrary amount of importers.
|
||||
*
|
||||
* A FeedsImporter holds a pointer to a FeedsFetcher, a FeedsParser and a
|
||||
* FeedsProcessor plugin. It further contains the configuration for itself and
|
||||
* each of the three plugins.
|
||||
*
|
||||
* Its most important responsibilities are configuration management, interfacing
|
||||
* with the job scheduler and expiring of all items produced by this
|
||||
* importer.
|
||||
*
|
||||
* When a FeedsImporter is instantiated, it loads its configuration. Then it
|
||||
* instantiates one fetcher, one parser and one processor plugin depending on
|
||||
* the configuration information. After instantiating them, it sets them to
|
||||
* the configuration information it holds for them.
|
||||
*/
|
||||
class FeedsImporter extends FeedsConfigurable {
|
||||
|
||||
// Every feed has a fetcher, a parser and a processor.
|
||||
// These variable names match the possible return values of
|
||||
// FeedsPlugin::typeOf().
|
||||
protected $fetcher, $parser, $processor;
|
||||
|
||||
// This array defines the variable names of the plugins above.
|
||||
protected $plugin_types = array('fetcher', 'parser', 'processor');
|
||||
|
||||
/**
|
||||
* Instantiate class variables, initialize and configure
|
||||
* plugins.
|
||||
*/
|
||||
protected function __construct($id) {
|
||||
parent::__construct($id);
|
||||
|
||||
// Try to load information from database.
|
||||
$this->load();
|
||||
|
||||
// Instantiate fetcher, parser and processor, set their configuration if
|
||||
// stored info is available.
|
||||
foreach ($this->plugin_types as $type) {
|
||||
$plugin = feeds_plugin($this->config[$type]['plugin_key'], $this->id);
|
||||
|
||||
if (isset($this->config[$type]['config'])) {
|
||||
$plugin->setConfig($this->config[$type]['config']);
|
||||
}
|
||||
$this->$type = $plugin;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove items older than $time.
|
||||
*
|
||||
* @param $time
|
||||
* All items older than REQUEST_TIME - $time will be deleted. If not
|
||||
* given, internal processor settings will be used.
|
||||
*
|
||||
* @return
|
||||
* FEEDS_BATCH_COMPLETE if the expiry process finished. A decimal between
|
||||
* 0.0 and 0.9 periodic if expiry is still in progress.
|
||||
*
|
||||
* @throws
|
||||
* Throws Exception if an error occurs when expiring items.
|
||||
*/
|
||||
public function expire($time = NULL) {
|
||||
return $this->processor->expire($time);
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedule all periodic tasks for this importer.
|
||||
*/
|
||||
public function schedule() {
|
||||
$this->scheduleExpire();
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedule expiry of items.
|
||||
*/
|
||||
public function scheduleExpire() {
|
||||
$job = array(
|
||||
'type' => $this->id,
|
||||
'period' => 0,
|
||||
'periodic' => TRUE,
|
||||
);
|
||||
if (FEEDS_EXPIRE_NEVER != $this->processor->expiryTime()) {
|
||||
$job['period'] = 3600;
|
||||
JobScheduler::get('feeds_importer_expire')->set($job);
|
||||
}
|
||||
else {
|
||||
JobScheduler::get('feeds_importer_expire')->remove($job);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Report how many items *should* be created on one page load by this
|
||||
* importer.
|
||||
*
|
||||
* Note:
|
||||
*
|
||||
* It depends on whether parser implements batching if this limit is actually
|
||||
* respected. Further, if no limit is reported it doesn't mean that the
|
||||
* number of items that can be created on one page load is actually without
|
||||
* limit.
|
||||
*
|
||||
* @return
|
||||
* A positive number defining the number of items that can be created on
|
||||
* one page load. 0 if this number is unlimited.
|
||||
*/
|
||||
public function getLimit() {
|
||||
return $this->processor->getLimit();
|
||||
}
|
||||
|
||||
/**
|
||||
* Save configuration.
|
||||
*/
|
||||
public function save() {
|
||||
$save = new stdClass();
|
||||
$save->id = $this->id;
|
||||
$save->config = $this->getConfig();
|
||||
|
||||
if ($config = db_query("SELECT config FROM {feeds_importer} WHERE id = :id", array(':id' => $this->id))->fetchField()) {
|
||||
drupal_write_record('feeds_importer', $save, 'id');
|
||||
// Only rebuild menu if content_type has changed. Don't worry about
|
||||
// rebuilding menus when creating a new importer since it will default
|
||||
// to the standalone page.
|
||||
$config = unserialize($config);
|
||||
if ($config['content_type'] != $save->config['content_type']) {
|
||||
variable_set('menu_rebuild_needed', TRUE);
|
||||
}
|
||||
}
|
||||
else {
|
||||
drupal_write_record('feeds_importer', $save);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load configuration and unpack.
|
||||
*/
|
||||
public function load() {
|
||||
ctools_include('export');
|
||||
if ($config = ctools_export_load_object('feeds_importer', 'conditions', array('id' => $this->id))) {
|
||||
$config = array_shift($config);
|
||||
$this->export_type = $config->export_type;
|
||||
$this->disabled = isset($config->disabled) ? $config->disabled : FALSE;
|
||||
$this->config = $config->config;
|
||||
return TRUE;
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete configuration. Removes configuration information
|
||||
* from database, does not delete configuration itself.
|
||||
*/
|
||||
public function delete() {
|
||||
db_delete('feeds_importer')
|
||||
->condition('id', $this->id)
|
||||
->execute();
|
||||
$job = array(
|
||||
'type' => $this->id,
|
||||
'id' => 0,
|
||||
);
|
||||
if ($this->export_type & EXPORT_IN_CODE) {
|
||||
feeds_reschedule($this->id);
|
||||
}
|
||||
else {
|
||||
JobScheduler::get('feeds_importer_expire')->remove($job);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set plugin.
|
||||
*
|
||||
* @param $plugin_key
|
||||
* A fetcher, parser or processor plugin.
|
||||
*
|
||||
* @todo Error handling, handle setting to the same plugin.
|
||||
*/
|
||||
public function setPlugin($plugin_key) {
|
||||
// $plugin_type can be either 'fetcher', 'parser' or 'processor'
|
||||
if ($plugin_type = FeedsPlugin::typeOf($plugin_key)) {
|
||||
if ($plugin = feeds_plugin($plugin_key, $this->id)) {
|
||||
// Unset existing plugin, switch to new plugin.
|
||||
unset($this->$plugin_type);
|
||||
$this->$plugin_type = $plugin;
|
||||
// Set configuration information, blow away any previous information on
|
||||
// this spot.
|
||||
$this->config[$plugin_type] = array('plugin_key' => $plugin_key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy a FeedsImporter configuration into this importer.
|
||||
*
|
||||
* @param FeedsImporter $importer
|
||||
* The feeds importer object to copy from.
|
||||
*/
|
||||
public function copy(FeedsConfigurable $configurable) {
|
||||
parent::copy($configurable);
|
||||
|
||||
if ($configurable instanceof FeedsImporter) {
|
||||
// Instantiate new fetcher, parser and processor and initialize their
|
||||
// configurations.
|
||||
foreach ($this->plugin_types as $plugin_type) {
|
||||
$this->setPlugin($configurable->config[$plugin_type]['plugin_key']);
|
||||
$this->$plugin_type->setConfig($configurable->config[$plugin_type]['config']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get configuration of this feed.
|
||||
*/
|
||||
public function getConfig() {
|
||||
foreach (array('fetcher', 'parser', 'processor') as $type) {
|
||||
$this->config[$type]['config'] = $this->$type->getConfig();
|
||||
}
|
||||
return parent::getConfig();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return defaults for feed configuration.
|
||||
*/
|
||||
public function configDefaults() {
|
||||
return array(
|
||||
'name' => '',
|
||||
'description' => '',
|
||||
'fetcher' => array(
|
||||
'plugin_key' => 'FeedsHTTPFetcher',
|
||||
),
|
||||
'parser' => array(
|
||||
'plugin_key' => 'FeedsSyndicationParser',
|
||||
),
|
||||
'processor' => array(
|
||||
'plugin_key' => 'FeedsNodeProcessor',
|
||||
),
|
||||
'content_type' => '',
|
||||
'update' => 0,
|
||||
'import_period' => 1800, // Refresh every 30 minutes by default.
|
||||
'expire_period' => 3600, // Expire every hour by default, this is a hidden setting.
|
||||
'import_on_create' => TRUE, // Import on submission.
|
||||
'process_in_background' => FALSE,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Override parent::configForm().
|
||||
*/
|
||||
public function configForm(&$form_state) {
|
||||
$config = $this->getConfig();
|
||||
$form = array();
|
||||
$form['name'] = array(
|
||||
'#type' => 'textfield',
|
||||
'#title' => t('Name'),
|
||||
'#description' => t('A human readable name of this importer.'),
|
||||
'#default_value' => $config['name'],
|
||||
'#required' => TRUE,
|
||||
);
|
||||
$form['description'] = array(
|
||||
'#type' => 'textfield',
|
||||
'#title' => t('Description'),
|
||||
'#description' => t('A description of this importer.'),
|
||||
'#default_value' => $config['description'],
|
||||
);
|
||||
$node_types = node_type_get_names();
|
||||
array_walk($node_types, 'check_plain');
|
||||
$form['content_type'] = array(
|
||||
'#type' => 'select',
|
||||
'#title' => t('Attach to content type'),
|
||||
'#description' => t('If "Use standalone form" is selected a source is imported by using a form under !import_form.
|
||||
If a content type is selected a source is imported by creating a node of that content type.',
|
||||
array('!import_form' => l(url('import', array('absolute' => TRUE)), 'import', array('attributes' => array('target' => '_new'))))),
|
||||
'#options' => array('' => t('Use standalone form')) + $node_types,
|
||||
'#default_value' => $config['content_type'],
|
||||
);
|
||||
$cron_required = ' ' . l(t('Requires cron to be configured.'), 'http://drupal.org/cron', array('attributes' => array('target' => '_new')));
|
||||
$period = drupal_map_assoc(array(900, 1800, 3600, 10800, 21600, 43200, 86400, 259200, 604800, 2419200), 'format_interval');
|
||||
foreach ($period as &$p) {
|
||||
$p = t('Every !p', array('!p' => $p));
|
||||
}
|
||||
$period = array(
|
||||
FEEDS_SCHEDULE_NEVER => t('Off'),
|
||||
0 => t('As often as possible'),
|
||||
) + $period;
|
||||
$form['import_period'] = array(
|
||||
'#type' => 'select',
|
||||
'#title' => t('Periodic import'),
|
||||
'#options' => $period,
|
||||
'#description' => t('Choose how often a source should be imported periodically.') . $cron_required,
|
||||
'#default_value' => $config['import_period'],
|
||||
);
|
||||
$form['import_on_create'] = array(
|
||||
'#type' => 'checkbox',
|
||||
'#title' => t('Import on submission'),
|
||||
'#description' => t('Check if import should be started at the moment a standalone form or node form is submitted.'),
|
||||
'#default_value' => $config['import_on_create'],
|
||||
);
|
||||
$form['process_in_background'] = array(
|
||||
'#type' => 'checkbox',
|
||||
'#title' => t('Process in background'),
|
||||
'#description' => t('For very large imports. If checked, import and delete tasks started from the web UI will be handled by a cron task in the background rather than by the browser. This does not affect periodic imports, they are handled by a cron task in any case.') . $cron_required,
|
||||
'#default_value' => $config['process_in_background'],
|
||||
);
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reschedule if import period changes.
|
||||
*/
|
||||
public function configFormSubmit(&$values) {
|
||||
if ($this->config['import_period'] != $values['import_period']) {
|
||||
feeds_reschedule($this->id);
|
||||
}
|
||||
parent::configFormSubmit($values);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper, see FeedsDataProcessor class.
|
||||
*/
|
||||
function feeds_format_expire($timestamp) {
|
||||
if ($timestamp == FEEDS_EXPIRE_NEVER) {
|
||||
return t('Never');
|
||||
}
|
||||
return t('after !time', array('!time' => format_interval($timestamp)));
|
||||
}
|
725
sites/all/modules/feeds/includes/FeedsSource.inc
Normal file
725
sites/all/modules/feeds/includes/FeedsSource.inc
Normal file
@@ -0,0 +1,725 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Definition of FeedsSourceInterface and FeedsSource class.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Distinguish exceptions occuring when handling locks.
|
||||
*/
|
||||
class FeedsLockException extends Exception {}
|
||||
|
||||
/**
|
||||
* Denote a import or clearing stage. Used for multi page processing.
|
||||
*/
|
||||
define('FEEDS_START', 'start_time');
|
||||
define('FEEDS_FETCH', 'fetch');
|
||||
define('FEEDS_PARSE', 'parse');
|
||||
define('FEEDS_PROCESS', 'process');
|
||||
define('FEEDS_PROCESS_CLEAR', 'process_clear');
|
||||
|
||||
/**
|
||||
* Declares an interface for a class that defines default values and form
|
||||
* descriptions for a FeedSource.
|
||||
*/
|
||||
interface FeedsSourceInterface {
|
||||
|
||||
/**
|
||||
* Crutch: for ease of use, we implement FeedsSourceInterface for every
|
||||
* plugin, but then we need to have a handle which plugin actually implements
|
||||
* a source.
|
||||
*
|
||||
* @see FeedsPlugin class.
|
||||
*
|
||||
* @return
|
||||
* TRUE if a plugin handles source specific configuration, FALSE otherwise.
|
||||
*/
|
||||
public function hasSourceConfig();
|
||||
|
||||
/**
|
||||
* Return an associative array of default values.
|
||||
*/
|
||||
public function sourceDefaults();
|
||||
|
||||
/**
|
||||
* Return a Form API form array that defines a form configuring values. Keys
|
||||
* correspond to the keys of the return value of sourceDefaults().
|
||||
*/
|
||||
public function sourceForm($source_config);
|
||||
|
||||
/**
|
||||
* Validate user entered values submitted by sourceForm().
|
||||
*/
|
||||
public function sourceFormValidate(&$source_config);
|
||||
|
||||
/**
|
||||
* A source is being saved.
|
||||
*/
|
||||
public function sourceSave(FeedsSource $source);
|
||||
|
||||
/**
|
||||
* A source is being deleted.
|
||||
*/
|
||||
public function sourceDelete(FeedsSource $source);
|
||||
}
|
||||
|
||||
/**
|
||||
* Status of an import or clearing operation on a source.
|
||||
*/
|
||||
class FeedsState {
|
||||
/**
|
||||
* Floating point number denoting the progress made. 0.0 meaning no progress
|
||||
* 1.0 = FEEDS_BATCH_COMPLETE meaning finished.
|
||||
*/
|
||||
public $progress;
|
||||
|
||||
/**
|
||||
* Used as a pointer to store where left off. Must be serializable.
|
||||
*/
|
||||
public $pointer;
|
||||
|
||||
/**
|
||||
* Natural numbers denoting more details about the progress being made.
|
||||
*/
|
||||
public $total;
|
||||
public $created;
|
||||
public $updated;
|
||||
public $deleted;
|
||||
public $skipped;
|
||||
public $failed;
|
||||
|
||||
/**
|
||||
* Constructor, initialize variables.
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->progress = FEEDS_BATCH_COMPLETE;
|
||||
$this->total =
|
||||
$this->created =
|
||||
$this->updated =
|
||||
$this->deleted =
|
||||
$this->skipped =
|
||||
$this->failed = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Safely report progress.
|
||||
*
|
||||
* When $total == $progress, the state of the task tracked by this state is
|
||||
* regarded to be complete.
|
||||
*
|
||||
* Handles the following cases gracefully:
|
||||
*
|
||||
* - $total is 0
|
||||
* - $progress is larger than $total
|
||||
* - $progress approximates $total so that $finished rounds to 1.0
|
||||
*
|
||||
* @param $total
|
||||
* A natural number that is the total to be worked off.
|
||||
* @param $progress
|
||||
* A natural number that is the progress made on $total.
|
||||
*/
|
||||
public function progress($total, $progress) {
|
||||
if ($progress > $total) {
|
||||
$this->progress = FEEDS_BATCH_COMPLETE;
|
||||
}
|
||||
elseif ($total) {
|
||||
$this->progress = $progress / $total;
|
||||
if ($this->progress == FEEDS_BATCH_COMPLETE && $total != $progress) {
|
||||
$this->progress = 0.99;
|
||||
}
|
||||
}
|
||||
else {
|
||||
$this->progress = FEEDS_BATCH_COMPLETE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This class encapsulates a source of a feed. It stores where the feed can be
|
||||
* found and how to import it.
|
||||
*
|
||||
* Information on how to import a feed is encapsulated in a FeedsImporter object
|
||||
* which is identified by the common id of the FeedsSource and the
|
||||
* FeedsImporter. More than one FeedsSource can use the same FeedsImporter
|
||||
* therefore a FeedsImporter never holds a pointer to a FeedsSource object, nor
|
||||
* does it hold any other information for a particular FeedsSource object.
|
||||
*
|
||||
* Classes extending FeedsPlugin can implement a sourceForm to expose
|
||||
* configuration for a FeedsSource object. This is for instance how FeedsFetcher
|
||||
* exposes a text field for a feed URL or how FeedsCSVParser exposes a select
|
||||
* field for choosing between colon or semicolon delimiters.
|
||||
*
|
||||
* It is important that a FeedsPlugin does not directly hold information about
|
||||
* a source but leave all storage up to FeedsSource. An instance of a
|
||||
* FeedsPlugin class only exists once per FeedsImporter configuration, while an
|
||||
* instance of a FeedsSource class exists once per feed_nid to be imported.
|
||||
*
|
||||
* As with FeedsImporter, the idea with FeedsSource is that it can be used
|
||||
* without actually saving the object to the database.
|
||||
*/
|
||||
class FeedsSource extends FeedsConfigurable {
|
||||
|
||||
// Contains the node id of the feed this source info object is attached to.
|
||||
// Equals 0 if not attached to any node - i. e. if used on a
|
||||
// standalone import form within Feeds or by other API users.
|
||||
protected $feed_nid;
|
||||
|
||||
// The FeedsImporter object that this source is expected to be used with.
|
||||
protected $importer;
|
||||
|
||||
// A FeedsSourceState object holding the current import/clearing state of this
|
||||
// source.
|
||||
protected $state;
|
||||
|
||||
// Fetcher result, used to cache fetcher result when batching.
|
||||
protected $fetcher_result;
|
||||
|
||||
// Timestamp when this source was imported the last time.
|
||||
protected $imported;
|
||||
|
||||
/**
|
||||
* Instantiate a unique object per class/id/feed_nid. Don't use
|
||||
* directly, use feeds_source() instead.
|
||||
*/
|
||||
public static function instance($importer_id, $feed_nid) {
|
||||
$class = variable_get('feeds_source_class', 'FeedsSource');
|
||||
static $instances = array();
|
||||
if (!isset($instances[$class][$importer_id][$feed_nid])) {
|
||||
$instances[$class][$importer_id][$feed_nid] = new $class($importer_id, $feed_nid);
|
||||
}
|
||||
return $instances[$class][$importer_id][$feed_nid];
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
protected function __construct($importer_id, $feed_nid) {
|
||||
$this->feed_nid = $feed_nid;
|
||||
$this->importer = feeds_importer($importer_id);
|
||||
parent::__construct($importer_id);
|
||||
$this->load();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the FeedsImporter object that this source is expected to be used with.
|
||||
*/
|
||||
public function importer() {
|
||||
return $this->importer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Preview = fetch and parse a feed.
|
||||
*
|
||||
* @return
|
||||
* FeedsParserResult object.
|
||||
*
|
||||
* @throws
|
||||
* Throws Exception if an error occurs when fetching or parsing.
|
||||
*/
|
||||
public function preview() {
|
||||
$result = $this->importer->fetcher->fetch($this);
|
||||
$result = $this->importer->parser->parse($this, $result);
|
||||
module_invoke_all('feeds_after_parse', $this, $result);
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start importing a source.
|
||||
*
|
||||
* This method starts an import job. Depending on the configuration of the
|
||||
* importer of this source, a Batch API job or a background job with Job
|
||||
* Scheduler will be created.
|
||||
*
|
||||
* @throws Exception
|
||||
* If processing in background is enabled, the first batch chunk of the
|
||||
* import will be executed on the current page request. This means that this
|
||||
* method may throw the same exceptions as FeedsSource::import().
|
||||
*/
|
||||
public function startImport() {
|
||||
$config = $this->importer->getConfig();
|
||||
if ($config['process_in_background']) {
|
||||
$this->startBackgroundJob('import');
|
||||
}
|
||||
else {
|
||||
$this->startBatchAPIJob(t('Importing'), 'import');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Start deleting all imported items of a source.
|
||||
*
|
||||
* This method starts a clear job. Depending on the configuration of the
|
||||
* importer of this source, a Batch API job or a background job with Job
|
||||
* Scheduler will be created.
|
||||
*
|
||||
* @throws Exception
|
||||
* If processing in background is enabled, the first batch chunk of the
|
||||
* clear task will be executed on the current page request. This means that
|
||||
* this method may throw the same exceptions as FeedsSource::clear().
|
||||
*/
|
||||
public function startClear() {
|
||||
$config = $this->importer->getConfig();
|
||||
if ($config['process_in_background']) {
|
||||
$this->startBackgroundJob('clear');
|
||||
}
|
||||
else {
|
||||
$this->startBatchAPIJob(t('Deleting'), 'clear');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedule all periodic tasks for this source.
|
||||
*/
|
||||
public function schedule() {
|
||||
$this->scheduleImport();
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedule periodic or background import tasks.
|
||||
*/
|
||||
public function scheduleImport() {
|
||||
// Check whether any fetcher is overriding the import period.
|
||||
$period = $this->importer->config['import_period'];
|
||||
$fetcher_period = $this->importer->fetcher->importPeriod($this);
|
||||
if (is_numeric($fetcher_period)) {
|
||||
$period = $fetcher_period;
|
||||
}
|
||||
$period = $this->progressImporting() === FEEDS_BATCH_COMPLETE ? $period : 0;
|
||||
$job = array(
|
||||
'type' => $this->id,
|
||||
'id' => $this->feed_nid,
|
||||
// Schedule as soon as possible if a batch is active.
|
||||
'period' => $period,
|
||||
'periodic' => TRUE,
|
||||
);
|
||||
if ($period != FEEDS_SCHEDULE_NEVER) {
|
||||
JobScheduler::get('feeds_source_import')->set($job);
|
||||
}
|
||||
else {
|
||||
JobScheduler::get('feeds_source_import')->remove($job);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedule background clearing tasks.
|
||||
*/
|
||||
public function scheduleClear() {
|
||||
$job = array(
|
||||
'type' => $this->id,
|
||||
'id' => $this->feed_nid,
|
||||
'period' => 0,
|
||||
'periodic' => TRUE,
|
||||
);
|
||||
// Remove job if batch is complete.
|
||||
if ($this->progressClearing() === FEEDS_BATCH_COMPLETE) {
|
||||
JobScheduler::get('feeds_source_clear')->remove($job);
|
||||
}
|
||||
// Schedule as soon as possible if batch is not complete.
|
||||
else {
|
||||
JobScheduler::get('feeds_source_clear')->set($job);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Import a source: execute fetching, parsing and processing stage.
|
||||
*
|
||||
* This method only executes the current batch chunk, then returns. If you are
|
||||
* looking to import an entire source, use FeedsSource::startImport() instead.
|
||||
*
|
||||
* @return
|
||||
* FEEDS_BATCH_COMPLETE if the import process finished. A decimal between
|
||||
* 0.0 and 0.9 periodic if import is still in progress.
|
||||
*
|
||||
* @throws
|
||||
* Throws Exception if an error occurs when importing.
|
||||
*/
|
||||
public function import() {
|
||||
$this->acquireLock();
|
||||
try {
|
||||
// If fetcher result is empty, we are starting a new import, log.
|
||||
if (empty($this->fetcher_result)) {
|
||||
$this->state[FEEDS_START] = time();
|
||||
}
|
||||
|
||||
// Fetch.
|
||||
if (empty($this->fetcher_result) || FEEDS_BATCH_COMPLETE == $this->progressParsing()) {
|
||||
$this->fetcher_result = $this->importer->fetcher->fetch($this);
|
||||
// Clean the parser's state, we are parsing an entirely new file.
|
||||
unset($this->state[FEEDS_PARSE]);
|
||||
}
|
||||
|
||||
// Parse.
|
||||
$parser_result = $this->importer->parser->parse($this, $this->fetcher_result);
|
||||
module_invoke_all('feeds_after_parse', $this, $parser_result);
|
||||
|
||||
// Process.
|
||||
$this->importer->processor->process($this, $parser_result);
|
||||
}
|
||||
catch (Exception $e) {
|
||||
// Do nothing.
|
||||
}
|
||||
$this->releaseLock();
|
||||
|
||||
// Clean up.
|
||||
$result = $this->progressImporting();
|
||||
if ($result == FEEDS_BATCH_COMPLETE || isset($e)) {
|
||||
$this->imported = time();
|
||||
$this->log('import', 'Imported in !s s', array('!s' => $this->imported - $this->state[FEEDS_START]), WATCHDOG_INFO);
|
||||
module_invoke_all('feeds_after_import', $this);
|
||||
unset($this->fetcher_result, $this->state);
|
||||
}
|
||||
$this->save();
|
||||
if (isset($e)) {
|
||||
throw $e;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all items from a feed.
|
||||
*
|
||||
* This method only executes the current batch chunk, then returns. If you are
|
||||
* looking to delete all items of a source, use FeedsSource::startClear()
|
||||
* instead.
|
||||
*
|
||||
* @return
|
||||
* FEEDS_BATCH_COMPLETE if the clearing process finished. A decimal between
|
||||
* 0.0 and 0.9 periodic if clearing is still in progress.
|
||||
*
|
||||
* @throws
|
||||
* Throws Exception if an error occurs when clearing.
|
||||
*/
|
||||
public function clear() {
|
||||
$this->acquireLock();
|
||||
try {
|
||||
$this->importer->fetcher->clear($this);
|
||||
$this->importer->parser->clear($this);
|
||||
$this->importer->processor->clear($this);
|
||||
}
|
||||
catch (Exception $e) {
|
||||
// Do nothing.
|
||||
}
|
||||
$this->releaseLock();
|
||||
|
||||
// Clean up.
|
||||
$result = $this->progressClearing();
|
||||
if ($result == FEEDS_BATCH_COMPLETE || isset($e)) {
|
||||
module_invoke_all('feeds_after_clear', $this);
|
||||
unset($this->state);
|
||||
}
|
||||
$this->save();
|
||||
if (isset($e)) {
|
||||
throw $e;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Report progress as float between 0 and 1. 1 = FEEDS_BATCH_COMPLETE.
|
||||
*/
|
||||
public function progressParsing() {
|
||||
return $this->state(FEEDS_PARSE)->progress;
|
||||
}
|
||||
|
||||
/**
|
||||
* Report progress as float between 0 and 1. 1 = FEEDS_BATCH_COMPLETE.
|
||||
*/
|
||||
public function progressImporting() {
|
||||
$fetcher = $this->state(FEEDS_FETCH);
|
||||
$parser = $this->state(FEEDS_PARSE);
|
||||
if ($fetcher->progress == FEEDS_BATCH_COMPLETE && $parser->progress == FEEDS_BATCH_COMPLETE) {
|
||||
return FEEDS_BATCH_COMPLETE;
|
||||
}
|
||||
// Fetching envelops parsing.
|
||||
// @todo: this assumes all fetchers neatly use total. May not be the case.
|
||||
$fetcher_fraction = $fetcher->total ? 1.0 / $fetcher->total : 1.0;
|
||||
$parser_progress = $parser->progress * $fetcher_fraction;
|
||||
$result = $fetcher->progress - $fetcher_fraction + $parser_progress;
|
||||
if ($result == FEEDS_BATCH_COMPLETE) {
|
||||
return 0.99;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Report progress on clearing.
|
||||
*/
|
||||
public function progressClearing() {
|
||||
return $this->state(FEEDS_PROCESS_CLEAR)->progress;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a state object for a given stage. Lazy instantiates new states.
|
||||
*
|
||||
* @todo Rename getConfigFor() accordingly to config().
|
||||
*
|
||||
* @param $stage
|
||||
* One of FEEDS_FETCH, FEEDS_PARSE, FEEDS_PROCESS or FEEDS_PROCESS_CLEAR.
|
||||
*
|
||||
* @return
|
||||
* The FeedsState object for the given stage.
|
||||
*/
|
||||
public function state($stage) {
|
||||
if (!is_array($this->state)) {
|
||||
$this->state = array();
|
||||
}
|
||||
if (!isset($this->state[$stage])) {
|
||||
$this->state[$stage] = new FeedsState();
|
||||
}
|
||||
return $this->state[$stage];
|
||||
}
|
||||
|
||||
/**
|
||||
* Count items imported by this source.
|
||||
*/
|
||||
public function itemCount() {
|
||||
return $this->importer->processor->itemCount($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Save configuration.
|
||||
*/
|
||||
public function save() {
|
||||
// Alert implementers of FeedsSourceInterface to the fact that we're saving.
|
||||
foreach ($this->importer->plugin_types as $type) {
|
||||
$this->importer->$type->sourceSave($this);
|
||||
}
|
||||
$config = $this->getConfig();
|
||||
|
||||
// Store the source property of the fetcher in a separate column so that we
|
||||
// can do fast lookups on it.
|
||||
$source = '';
|
||||
if (isset($config[get_class($this->importer->fetcher)]['source'])) {
|
||||
$source = $config[get_class($this->importer->fetcher)]['source'];
|
||||
}
|
||||
$object = array(
|
||||
'id' => $this->id,
|
||||
'feed_nid' => $this->feed_nid,
|
||||
'imported' => $this->imported,
|
||||
'config' => $config,
|
||||
'source' => $source,
|
||||
'state' => isset($this->state) ? $this->state : FALSE,
|
||||
'fetcher_result' => isset($this->fetcher_result) ? $this->fetcher_result : FALSE,
|
||||
);
|
||||
if (db_query_range("SELECT 1 FROM {feeds_source} WHERE id = :id AND feed_nid = :nid", 0, 1, array(':id' => $this->id, ':nid' => $this->feed_nid))->fetchField()) {
|
||||
drupal_write_record('feeds_source', $object, array('id', 'feed_nid'));
|
||||
}
|
||||
else {
|
||||
drupal_write_record('feeds_source', $object);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load configuration and unpack.
|
||||
*
|
||||
* @todo Patch CTools to move constants from export.inc to ctools.module.
|
||||
*/
|
||||
public function load() {
|
||||
if ($record = db_query("SELECT imported, config, state, fetcher_result FROM {feeds_source} WHERE id = :id AND feed_nid = :nid", array(':id' => $this->id, ':nid' => $this->feed_nid))->fetchObject()) {
|
||||
// While FeedsSource cannot be exported, we still use CTool's export.inc
|
||||
// export definitions.
|
||||
ctools_include('export');
|
||||
$this->export_type = EXPORT_IN_DATABASE;
|
||||
$this->imported = $record->imported;
|
||||
$this->config = unserialize($record->config);
|
||||
if (!empty($record->state)) {
|
||||
$this->state = unserialize($record->state);
|
||||
}
|
||||
if (!empty($record->fetcher_result)) {
|
||||
$this->fetcher_result = unserialize($record->fetcher_result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete configuration. Removes configuration information
|
||||
* from database, does not delete configuration itself.
|
||||
*/
|
||||
public function delete() {
|
||||
// Alert implementers of FeedsSourceInterface to the fact that we're
|
||||
// deleting.
|
||||
foreach ($this->importer->plugin_types as $type) {
|
||||
$this->importer->$type->sourceDelete($this);
|
||||
}
|
||||
db_delete('feeds_source')
|
||||
->condition('id', $this->id)
|
||||
->condition('feed_nid', $this->feed_nid)
|
||||
->execute();
|
||||
// Remove from schedule.
|
||||
$job = array(
|
||||
'type' => $this->id,
|
||||
'id' => $this->feed_nid,
|
||||
);
|
||||
JobScheduler::get('feeds_source_import')->remove($job);
|
||||
}
|
||||
|
||||
/**
|
||||
* Only return source if configuration is persistent and valid.
|
||||
*
|
||||
* @see FeedsConfigurable::existing().
|
||||
*/
|
||||
public function existing() {
|
||||
// If there is no feed nid given, there must be no content type specified.
|
||||
// If there is a feed nid given, there must be a content type specified.
|
||||
// Ensure that importer is persistent (= defined in code or DB).
|
||||
// Ensure that source is persistent (= defined in DB).
|
||||
if ((empty($this->feed_nid) && empty($this->importer->config['content_type'])) ||
|
||||
(!empty($this->feed_nid) && !empty($this->importer->config['content_type']))) {
|
||||
$this->importer->existing();
|
||||
return parent::existing();
|
||||
}
|
||||
throw new FeedsNotExistingException(t('Source configuration not valid.'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the configuration for a specific client class.
|
||||
*
|
||||
* @param FeedsSourceInterface $client
|
||||
* An object that is an implementer of FeedsSourceInterface.
|
||||
*
|
||||
* @return
|
||||
* An array stored for $client.
|
||||
*/
|
||||
public function getConfigFor(FeedsSourceInterface $client) {
|
||||
$class = get_class($client);
|
||||
return isset($this->config[$class]) ? $this->config[$class] : $client->sourceDefaults();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the configuration for a specific client class.
|
||||
*
|
||||
* @param FeedsSourceInterface $client
|
||||
* An object that is an implementer of FeedsSourceInterface.
|
||||
* @param $config
|
||||
* The configuration for $client.
|
||||
*
|
||||
* @return
|
||||
* An array stored for $client.
|
||||
*/
|
||||
public function setConfigFor(FeedsSourceInterface $client, $config) {
|
||||
$this->config[get_class($client)] = $config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return defaults for feed configuration.
|
||||
*/
|
||||
public function configDefaults() {
|
||||
// Collect information from plugins.
|
||||
$defaults = array();
|
||||
foreach ($this->importer->plugin_types as $type) {
|
||||
if ($this->importer->$type->hasSourceConfig()) {
|
||||
$defaults[get_class($this->importer->$type)] = $this->importer->$type->sourceDefaults();
|
||||
}
|
||||
}
|
||||
return $defaults;
|
||||
}
|
||||
|
||||
/**
|
||||
* Override parent::configForm().
|
||||
*/
|
||||
public function configForm(&$form_state) {
|
||||
// Collect information from plugins.
|
||||
$form = array();
|
||||
foreach ($this->importer->plugin_types as $type) {
|
||||
if ($this->importer->$type->hasSourceConfig()) {
|
||||
$class = get_class($this->importer->$type);
|
||||
$config = isset($this->config[$class]) ? $this->config[$class] : array();
|
||||
$form[$class] = $this->importer->$type->sourceForm($config);
|
||||
$form[$class]['#tree'] = TRUE;
|
||||
}
|
||||
}
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Override parent::configFormValidate().
|
||||
*/
|
||||
public function configFormValidate(&$values) {
|
||||
foreach ($this->importer->plugin_types as $type) {
|
||||
$class = get_class($this->importer->$type);
|
||||
if (isset($values[$class]) && $this->importer->$type->hasSourceConfig()) {
|
||||
$this->importer->$type->sourceFormValidate($values[$class]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes to feeds log.
|
||||
*/
|
||||
public function log($type, $message, $variables = array(), $severity = WATCHDOG_NOTICE) {
|
||||
feeds_log($this->id, $this->feed_nid, $type, $message, $variables, $severity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Background job helper. Starts a background job using Job Scheduler.
|
||||
*
|
||||
* Execute the first batch chunk of a background job on the current page load,
|
||||
* moves the rest of the job processing to a cron powered background job.
|
||||
*
|
||||
* Executing the first batch chunk is important, otherwise, when a user
|
||||
* submits a source for import or clearing, we will leave her without any
|
||||
* visual indicators of an ongoing job.
|
||||
*
|
||||
* @see FeedsSource::startImport().
|
||||
* @see FeedsSource::startClear().
|
||||
*
|
||||
* @param $method
|
||||
* Method to execute on importer; one of 'import' or 'clear'.
|
||||
*
|
||||
* @throws Exception $e
|
||||
*/
|
||||
protected function startBackgroundJob($method) {
|
||||
if (FEEDS_BATCH_COMPLETE != $this->$method()) {
|
||||
$job = array(
|
||||
'type' => $this->id,
|
||||
'id' => $this->feed_nid,
|
||||
'period' => 0,
|
||||
'periodic' => FALSE,
|
||||
);
|
||||
JobScheduler::get("feeds_source_{$method}")->set($job);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Batch API helper. Starts a Batch API job.
|
||||
*
|
||||
* @see FeedsSource::startImport().
|
||||
* @see FeedsSource::startClear().
|
||||
* @see feeds_batch()
|
||||
*
|
||||
* @param $title
|
||||
* Title to show to user when executing batch.
|
||||
* @param $method
|
||||
* Method to execute on importer; one of 'import' or 'clear'.
|
||||
*/
|
||||
protected function startBatchAPIJob($title, $method) {
|
||||
$batch = array(
|
||||
'title' => $title,
|
||||
'operations' => array(
|
||||
array('feeds_batch', array($method, $this->id, $this->feed_nid)),
|
||||
),
|
||||
'progress_message' => '',
|
||||
);
|
||||
batch_set($batch);
|
||||
}
|
||||
|
||||
/**
|
||||
* Acquires a lock for this source.
|
||||
*
|
||||
* @throws FeedsLockException
|
||||
* If a lock for the requested job could not be acquired.
|
||||
*/
|
||||
protected function acquireLock() {
|
||||
if (!lock_acquire("feeds_source_{$this->id}_{$this->feed_nid}", 60.0)) {
|
||||
throw new FeedsLockException(t('Cannot acquire lock for source @id / @feed_nid.', array('@id' => $this->id, '@feed_nid' => $this->feed_nid)));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Releases a lock for this source.
|
||||
*/
|
||||
protected function releaseLock() {
|
||||
lock_release("feeds_source_{$this->id}_{$this->feed_nid}");
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user