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
<?php
// $Id$
/**
* @file
* Definition of FeedsSourceInterface and FeedsSource class.
*/
/**
* 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(&$values);
}
/**
* 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.
Alex Barth
committed
*
* FeedsPlugins that implement FeedsSourceInterface and return TRUE on
* hasSourceConfig() can define default values and forms that allow for
* configuration of the FeedsSource. For an example, see FeedsFetcher and
* FeedsHTTPFetcher.
*
* 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 (@todo: needs to be
* tested).
Alex Barth
committed
*/
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;
/**
* 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 = 0) {
Alex Barth
committed
$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);
Alex Barth
committed
}
return $instances[$class][$importer_id][$feed_nid];
Alex Barth
committed
}
/**
* Constructor.
*/
protected function __construct($importer_id, $feed_nid) {
Alex Barth
committed
$this->feed_nid = $feed_nid;
$this->importer = feeds_importer($importer_id);
parent::__construct($importer_id);
Alex Barth
committed
$this->load();
}
/**
* Import a feed: execute, fetching, parsing and processing stage.
*
* @todo: Iron out and document potential Exceptions.
* @todo: Support batching.
* @todo: catch exceptions outside of import(), clear() and expire().
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
*/
public function import() {
try {
$result = $this->importer->fetcher->fetch($this);
$result = $this->importer->parser->parse($result, $this);
$this->importer->processor->process($result, $this);
}
catch (Exception $e) {
drupal_set_message($e->getMessage(), 'error');
}
module_invoke_all('feeds_after_import', $this->importer, $this);
}
/**
* Remove all items from a feed.
*/
public function clear() {
try {
$this->importer->fetcher->clear($this);
$this->importer->parser->clear($this);
$this->importer->processor->clear($this);
}
catch (Exception $e) {
drupal_set_message($e->getMessage(), 'error');
}
}
Alex Barth
committed
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
180
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
/**
* Save configuration.
*/
public function save() {
$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,
'config' => $config,
'source' => $source,
);
// Make sure a source record is present at all time, try to update first,
// then insert.
drupal_write_record('feeds_source', $object, array('id', 'feed_nid'));
if (!db_affected_rows()) {
drupal_write_record('feeds_source', $object);
}
}
/**
* Load configuration and unpack.
*/
public function load() {
if ($config = db_result(db_query('SELECT config FROM {feeds_source} WHERE id = "%s" AND feed_nid = %d', $this->id, $this->feed_nid))) {
// While FeedsSource cannot be exported, we still use CTool's export.inc
// export definitions.
// @todo: patch CTools to move constants from export.inc to ctools.module.
ctools_include('export');
$this->export_type = EXPORT_IN_DATABASE;
$this->config = unserialize($config);
}
}
/**
* Delete configuration. Removes configuration information
* from database, does not delete configuration itself.
*/
public function delete() {
db_query('DELETE FROM {feeds_source} WHERE id = "%s" AND feed_nid = %d', $this->id, $this->feed_nid);
}
/**
* Convenience function. Returns the configuration for a specific class.
*
* @param FeedsSourceInterface $client
* An object that is an implementer of FeedsSourceInterface.
*
* @return
* An array stored for $client.
*/
public function getConfigFor(FeedsSourceInterface $client) {
return $this->config[get_class($client)];
}
/**
* 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);
$form[$class] = $this->importer->$type->sourceForm($this->config[$class]);
$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]);
}
}
}
}