1 | <?php |
---|
2 | |
---|
3 | /** |
---|
4 | * @file |
---|
5 | * FeedsImporter class and related. |
---|
6 | */ |
---|
7 | |
---|
8 | // Including FeedsImporter.inc automatically includes dependencies. |
---|
9 | require_once(dirname(__FILE__) .'/FeedsConfigurable.inc'); |
---|
10 | require_once(dirname(__FILE__) .'/FeedsSource.inc'); |
---|
11 | require_once(dirname(__FILE__) .'/FeedsBatch.inc'); |
---|
12 | |
---|
13 | /** |
---|
14 | * A FeedsImporter object describes how an external source should be fetched, |
---|
15 | * parsed and processed. Feeds can manage an arbitrary amount of importers. |
---|
16 | * |
---|
17 | * A FeedsImporter holds a pointer to a FeedsFetcher, a FeedsParser and a |
---|
18 | * FeedsProcessor plugin. It further contains the configuration for itself and |
---|
19 | * each of the three plugins. |
---|
20 | * |
---|
21 | * Its most important responsibilities are configuration management, interfacing |
---|
22 | * with the job scheduler and expiring of all items produced by this |
---|
23 | * importer. |
---|
24 | * |
---|
25 | * When a FeedsImporter is instantiated, it loads its configuration. Then it |
---|
26 | * instantiates one fetcher, one parser and one processor plugin depending on |
---|
27 | * the configuration information. After instantiating them, it sets them to |
---|
28 | * the configuration information it holds for them. |
---|
29 | */ |
---|
30 | class FeedsImporter extends FeedsConfigurable { |
---|
31 | |
---|
32 | // Every feed has a fetcher, a parser and a processor. |
---|
33 | // These variable names match the possible return values of |
---|
34 | // feeds_plugin_type(). |
---|
35 | protected $fetcher, $parser, $processor; |
---|
36 | |
---|
37 | // This array defines the variable names of the plugins above. |
---|
38 | protected $plugin_types = array('fetcher', 'parser', 'processor'); |
---|
39 | |
---|
40 | /** |
---|
41 | * Instantiate class variables, initialize and configure |
---|
42 | * plugins. |
---|
43 | */ |
---|
44 | protected function __construct($id) { |
---|
45 | parent::__construct($id); |
---|
46 | |
---|
47 | // Try to load information from database. |
---|
48 | $this->load(); |
---|
49 | |
---|
50 | // Instantiate fetcher, parser and processor, set their configuration if |
---|
51 | // stored info is available. |
---|
52 | foreach ($this->plugin_types as $type) { |
---|
53 | $plugin = feeds_plugin_instance($this->config[$type]['plugin_key'], $this->id); |
---|
54 | |
---|
55 | if (isset($this->config[$type]['config'])) { |
---|
56 | $plugin->setConfig($this->config[$type]['config']); |
---|
57 | } |
---|
58 | $this->$type = $plugin; |
---|
59 | } |
---|
60 | } |
---|
61 | |
---|
62 | /** |
---|
63 | * Remove items older than $time. |
---|
64 | * |
---|
65 | * @param $time |
---|
66 | * All items older than FEEDS_REQUEST_TIME - $time will be deleted. If not |
---|
67 | * given, internal processor settings will be used. |
---|
68 | * |
---|
69 | * @return |
---|
70 | * FEEDS_BATCH_COMPLETE if the expiry process finished. A decimal between |
---|
71 | * 0.0 and 0.9 periodic if expiry is still in progress. |
---|
72 | * |
---|
73 | * @throws |
---|
74 | * Throws Exception if an error occurs when expiring items. |
---|
75 | */ |
---|
76 | public function expire($time = NULL) { |
---|
77 | return $this->processor->expire($time); |
---|
78 | } |
---|
79 | |
---|
80 | /** |
---|
81 | * Schedule this importer. |
---|
82 | */ |
---|
83 | public function schedule() { |
---|
84 | $job = array( |
---|
85 | 'callback' => 'feeds_importer_expire', |
---|
86 | 'type' => $this->id, |
---|
87 | 'period' => 0, |
---|
88 | 'periodic' => TRUE, |
---|
89 | ); |
---|
90 | if (FEEDS_EXPIRE_NEVER != $this->processor->expiryTime()) { |
---|
91 | $job['period'] = 3600; |
---|
92 | job_scheduler()->set($job); |
---|
93 | } |
---|
94 | else { |
---|
95 | job_scheduler()->remove($job); |
---|
96 | } |
---|
97 | } |
---|
98 | |
---|
99 | /** |
---|
100 | * Save configuration. |
---|
101 | */ |
---|
102 | public function save() { |
---|
103 | $save = new stdClass(); |
---|
104 | $save->id = $this->id; |
---|
105 | $save->config = $this->getConfig(); |
---|
106 | |
---|
107 | if ($config = db_result(db_query("SELECT config FROM {feeds_importer} WHERE id = '%s'", $this->id))) { |
---|
108 | drupal_write_record('feeds_importer', $save, 'id'); |
---|
109 | // Only rebuild menu if content_type has changed. Don't worry about |
---|
110 | // rebuilding menus when creating a new importer since it will default |
---|
111 | // to the standalone page. |
---|
112 | $config = unserialize($config); |
---|
113 | if ($config['content_type'] != $save->config['content_type']) { |
---|
114 | variable_set('menu_rebuild_needed', TRUE); |
---|
115 | } |
---|
116 | } |
---|
117 | else { |
---|
118 | drupal_write_record('feeds_importer', $save); |
---|
119 | } |
---|
120 | } |
---|
121 | |
---|
122 | /** |
---|
123 | * Load configuration and unpack. |
---|
124 | */ |
---|
125 | public function load() { |
---|
126 | ctools_include('export'); |
---|
127 | if ($config = ctools_export_load_object('feeds_importer', 'conditions', array('id' => $this->id))) { |
---|
128 | $config = array_shift($config); |
---|
129 | $this->export_type = $config->export_type; |
---|
130 | $this->disabled = isset($config->disabled) ? $config->disabled : FALSE; |
---|
131 | $this->config = $config->config; |
---|
132 | return TRUE; |
---|
133 | } |
---|
134 | return FALSE; |
---|
135 | } |
---|
136 | |
---|
137 | /** |
---|
138 | * Delete configuration. Removes configuration information |
---|
139 | * from database, does not delete configuration itself. |
---|
140 | */ |
---|
141 | public function delete() { |
---|
142 | db_query("DELETE FROM {feeds_importer} WHERE id = '%s'", $this->id); |
---|
143 | $job = array( |
---|
144 | 'callback' => 'feeds_importer_expire', |
---|
145 | 'type' => $this->id, |
---|
146 | 'id' => 0, |
---|
147 | ); |
---|
148 | if ($this->export_type & EXPORT_IN_CODE) { |
---|
149 | feeds_reschedule($this->id); |
---|
150 | } |
---|
151 | else { |
---|
152 | job_scheduler()->remove($job); |
---|
153 | } |
---|
154 | } |
---|
155 | |
---|
156 | /** |
---|
157 | * Set plugin. |
---|
158 | * |
---|
159 | * @param $plugin_key |
---|
160 | * A fetcher, parser or processor plugin. |
---|
161 | * |
---|
162 | * @todo Error handling, handle setting to the same plugin. |
---|
163 | */ |
---|
164 | public function setPlugin($plugin_key) { |
---|
165 | // $plugin_type can be either 'fetcher', 'parser' or 'processor' |
---|
166 | if ($plugin_type = feeds_plugin_type($plugin_key)) { |
---|
167 | if ($plugin = feeds_plugin_instance($plugin_key, $this->id)) { |
---|
168 | // Unset existing plugin, switch to new plugin. |
---|
169 | unset($this->$plugin_type); |
---|
170 | $this->$plugin_type = $plugin; |
---|
171 | // Set configuration information, blow away any previous information on |
---|
172 | // this spot. |
---|
173 | $this->config[$plugin_type] = array('plugin_key' => $plugin_key); |
---|
174 | } |
---|
175 | } |
---|
176 | } |
---|
177 | |
---|
178 | /** |
---|
179 | * Copy a FeedsImporter configuration into this importer. |
---|
180 | * |
---|
181 | * @param FeedsImporter $importer |
---|
182 | * The feeds importer object to copy from. |
---|
183 | */ |
---|
184 | public function copy(FeedsConfigurable $importer) { |
---|
185 | $this->setConfig($importer->config); |
---|
186 | |
---|
187 | // Instantiate new fetcher, parser and processor and initialize their |
---|
188 | // configurations. |
---|
189 | foreach ($this->plugin_types as $plugin_type) { |
---|
190 | $this->setPlugin($importer->config[$plugin_type]['plugin_key']); |
---|
191 | $this->$plugin_type->setConfig($importer->config[$plugin_type]['config']); |
---|
192 | } |
---|
193 | } |
---|
194 | |
---|
195 | /** |
---|
196 | * Get configuration of this feed. |
---|
197 | */ |
---|
198 | public function getConfig() { |
---|
199 | foreach (array('fetcher', 'parser', 'processor') as $type) { |
---|
200 | $this->config[$type]['config'] = $this->$type->getConfig(); |
---|
201 | } |
---|
202 | return $this->config;// Collect information from plugins. |
---|
203 | } |
---|
204 | |
---|
205 | /** |
---|
206 | * Return defaults for feed configuration. |
---|
207 | */ |
---|
208 | public function configDefaults() { |
---|
209 | return array( |
---|
210 | 'name' => '', |
---|
211 | 'description' => '', |
---|
212 | 'fetcher' => array( |
---|
213 | 'plugin_key' => 'FeedsHTTPFetcher', |
---|
214 | ), |
---|
215 | 'parser' => array( |
---|
216 | 'plugin_key' => 'FeedsSyndicationParser', |
---|
217 | ), |
---|
218 | 'processor' => array( |
---|
219 | 'plugin_key' => 'FeedsNodeProcessor', |
---|
220 | ), |
---|
221 | 'content_type' => '', |
---|
222 | 'update' => 0, |
---|
223 | 'import_period' => 1800, // Refresh every 30 minutes by default. |
---|
224 | 'expire_period' => 3600, // Expire every hour by default, this is a hidden setting. |
---|
225 | 'import_on_create' => TRUE, // Import on create. |
---|
226 | ); |
---|
227 | } |
---|
228 | |
---|
229 | /** |
---|
230 | * Override parent::configForm(). |
---|
231 | */ |
---|
232 | public function configForm(&$form_state) { |
---|
233 | $form = array(); |
---|
234 | $form['name'] = array( |
---|
235 | '#type' => 'textfield', |
---|
236 | '#title' => t('Name'), |
---|
237 | '#description' => t('The name of this configuration.'), |
---|
238 | '#default_value' => $this->config['name'], |
---|
239 | '#required' => TRUE, |
---|
240 | ); |
---|
241 | $form['description'] = array( |
---|
242 | '#type' => 'textfield', |
---|
243 | '#title' => t('Description'), |
---|
244 | '#description' => t('A description of this configuration.'), |
---|
245 | '#default_value' => $this->config['description'], |
---|
246 | ); |
---|
247 | $form['content_type'] = array( |
---|
248 | '#type' => 'select', |
---|
249 | '#title' => t('Attach to content type'), |
---|
250 | '#description' => t('If an importer is attached to a content type, content is imported by creating a node. If the standalone form is selected, content is imported by using the standalone form under http://example.com/import.'), |
---|
251 | '#options' => array('' => t('Use standalone form')) + node_get_types('names'), |
---|
252 | '#default_value' => $this->config['content_type'], |
---|
253 | ); |
---|
254 | $period = drupal_map_assoc(array(0, 900, 1800, 3600, 10800, 21600, 43200, 86400, 259200, 604800, 2419200), 'format_interval'); |
---|
255 | $period[FEEDS_SCHEDULE_NEVER] = t('Never'); |
---|
256 | $period[0] = t('As often as possible'); |
---|
257 | $form['import_period'] = array( |
---|
258 | '#type' => 'select', |
---|
259 | '#title' => t('Minimum refresh period'), |
---|
260 | '#options' => $period, |
---|
261 | '#description' => t('This is the minimum time that must elapse before a feed may be refreshed automatically.'), |
---|
262 | '#default_value' => $this->config['import_period'], |
---|
263 | ); |
---|
264 | $form['import_on_create'] = array( |
---|
265 | '#type' => 'checkbox', |
---|
266 | '#title' => t('Import on submission'), |
---|
267 | '#description' => t('Check if content should be imported at the moment of feed submission.'), |
---|
268 | '#default_value' => $this->config['import_on_create'], |
---|
269 | ); |
---|
270 | return $form; |
---|
271 | } |
---|
272 | |
---|
273 | /** |
---|
274 | * Reschedule if import period changes. |
---|
275 | */ |
---|
276 | public function configFormSubmit(&$values) { |
---|
277 | if ($this->config['import_period'] != $values['import_period']) { |
---|
278 | feeds_reschedule($this->id); |
---|
279 | } |
---|
280 | parent::configFormSubmit($values); |
---|
281 | } |
---|
282 | } |
---|
283 | |
---|
284 | /** |
---|
285 | * Helper, see FeedsDataProcessor class. |
---|
286 | */ |
---|
287 | function feeds_format_expire($timestamp) { |
---|
288 | if ($timestamp == FEEDS_EXPIRE_NEVER) { |
---|
289 | return t('Never'); |
---|
290 | } |
---|
291 | return t('after !time', array('!time' => format_interval($timestamp))); |
---|
292 | } |
---|