Newer
Older
Alex Barth
committed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
<?php
// $Id$
/**
* @file
* FeedsImporter class and related.
*/
// Including FeedsImporter.inc automatically includes dependencies.
require_once(dirname(__FILE__) .'/FeedsConfigurable.inc');
require_once(dirname(__FILE__) .'/FeedsSource.inc');
/**
* A Feeds result class.
*
* @see class FeedsFetcherResult
* @see class FeedsParserResult
*/
abstract class FeedsResult {
// An array of valid values for $type.
protected $valid_types = array();
// The type of this result.
protected $type;
// The value of this result.
protected $value;
/**
* Constructor: create object, validate class variables.
*
* @param $value
* The value of this result.
* @param $type
* The type of this result. Must be one of $valid_types.
*/
public function __construct($value, $type) {
$this->__set('type', $type);
$this->__set('value', $value);
}
/**
* Control access to class variables.
*/
public function __set($name, $value) {
if ($name == 'valid_types') {
throw new Exception(t('Cannot write FeedsResult::valid_types.'));
}
if ($name == 'type') {
if (!in_array($value, $this->valid_types)) {
throw new Exception(t('Invalid type "!type"', array('!type' => $value)));
}
}
$this->$name = $value;
}
/**
* Control access to class variables.
*/
public function __get($name) {
return $this->$name;
}
}
/**
* Class defining an importer object. This is the main hub for Feeds module's
* functionality.
*
* A FeedsImporter holds a pointer to a fetcher, a parser and a processor
* plugin. It further contains the configuration for itself and each of the
* three plugins.
*
* Its most important responsibilities are configuration management, importing
* and purging.
*
* 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.
*
* @see __construct()
*
* When importing or purging, a FeedsSource object is passed into import() and
* the fetcher, the parser and the processor are subsequently executed. It is
* important to note that at no time a FeedsImporter object holds a pointer to a
* FeedsSource object, while a FeedsSource object always holds a pointer to a
* FeedsImporter object. The reason is that there is only one FeedsImporter
* instance per configuration, while there is a FeedsSource object per source to
* be imported. Sources can be tied to feed nodes, thus there can be potentially
* many sources per feeds configuration.
*
* @see import()
* @see clear()
*/
class FeedsImporter extends FeedsConfigurable {
// Every feed has a fetcher, a parser and a processor.
// These variable names match the possible return values of
// feeds_plugin_type().
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_instance($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. If $time is not given, processor settings
* will be used.
*/
public function expire($time = NULL) {
try {
$this->processor->expire($time);
}
catch (Exception $e) {
drupal_set_message($e->getMessage(), 'error');
}
}
/**
* Get the refresh period for import() or expire().
*/
public function getSchedulePeriod($callback) {
if ($callback == 'import') {
return $this->config['import_period'];
}
if ($callback == 'expire') {
// If a processor has expiry time set, run expiry every hour.
if (FEEDS_EXPIRE_NEVER != $this->processor->expiryTime()) {
return 3600;
}
return FEEDS_SCHEDULE_NEVER;
}
}
/**
* Save configuration.
*/
public function save() {
$save = new stdClass();
$save->id = $this->id;
$save->config = $this->getConfig();
// Make sure a source record is present at all time, try to update first,
// then insert.
drupal_write_record('feeds_importer', $save, 'id');
if (!db_affected_rows()) {
drupal_write_record('feeds_importer', $save);
}
// Clear menu cache, changes to importer can change menu items.
menu_rebuild();
}
/**
* 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;
Alex Barth
committed
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
$this->config = $config->config;
return TRUE;
}
return FALSE;
}
/**
* Delete configuration. Removes configuration information
* from database, does not delete configuration itself.
*/
public function delete() {
db_query('DELETE FROM {feeds_importer} WHERE id = "%s"', $this->id);
}
/**
* 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 = feeds_plugin_type($plugin_key)) {
if ($plugin = feeds_plugin_instance($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(FeedsImporter $importer) {
$this->setConfig($importer->config);
// Instantiate new fetcher, parser and processor and initialize their
// configurations.
foreach ($this->plugin_types as $plugin_type) {
$this->setPlugin($importer->config[$plugin_type]['plugin_key']);
$this->$plugin_type->setConfig($importer->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 $this->config;// Collect information from plugins.
}
/**
* 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' => 'page', // @todo: provide default content type feed.
'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 create.
);
}
/**
* Override parent::configForm().
*/
public function configForm(&$form_state) {
$form = array();
$form['name'] = array(
'#type' => 'textfield',
'#title' => t('Name'),
'#description' => t('The name of this configuration.'),
'#default_value' => $this->config['name'],
'#required' => TRUE,
);
$form['description'] = array(
'#type' => 'textfield',
'#title' => t('Description'),
'#description' => t('A description of this configuration.'),
'#default_value' => $this->config['description'],
);
$form['content_type'] = array(
'#type' => 'select',
'#title' => t('Attach to content type'),
'#description' => t('If you attach a configuration to a node you can use nodes for creating feeds on your site.'),
'#options' => array('' => t('Use standalone form')) + node_get_types('names'),
Alex Barth
committed
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
'#default_value' => $this->config['content_type'],
);
$period = drupal_map_assoc(array(0, 900, 1800, 3600, 10800, 21600, 43200, 86400, 259200, 604800, 2419200), 'format_interval');
$period[FEEDS_SCHEDULE_NEVER] = t('Never');
$period[0] = t('As often as possible');
$form['import_period'] = array(
'#type' => 'select',
'#title' => t('Minimum refresh period'),
'#options' => $period,
'#description' => t('This is the minimum time that must elapse before a feed may be refreshed automatically.'),
'#default_value' => $this->config['import_period'],
);
$form['import_on_create'] = array(
'#type' => 'checkbox',
'#title' => t('Import on create'),
'#description' => t('Check if content should be imported at the moment of feed submission.'),
'#default_value' => $this->config['import_on_create'],
);
return $form;
}
}
/**
* 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)));
}