Projet

Général

Profil

Paste
Télécharger (10,7 ko) Statistiques
| Branche: | Révision:

root / drupal7 / sites / all / modules / feeds / includes / FeedsImporter.inc @ 2c8c2b87

1
<?php
2

    
3
/**
4
 * @file
5
 * FeedsImporter class and related.
6
 */
7

    
8
/**
9
 * A FeedsImporter object describes how an external source should be fetched,
10
 * parsed and processed. Feeds can manage an arbitrary amount of importers.
11
 *
12
 * A FeedsImporter holds a pointer to a FeedsFetcher, a FeedsParser and a
13
 * FeedsProcessor plugin. It further contains the configuration for itself and
14
 * each of the three plugins.
15
 *
16
 * Its most important responsibilities are configuration management, interfacing
17
 * with the job scheduler and expiring of all items produced by this
18
 * importer.
19
 *
20
 * When a FeedsImporter is instantiated, it loads its configuration. Then it
21
 * instantiates one fetcher, one parser and one processor plugin depending on
22
 * the configuration information. After instantiating them, it sets them to
23
 * the configuration information it holds for them.
24
 */
25
class FeedsImporter extends FeedsConfigurable {
26

    
27
  // Every feed has a fetcher, a parser and a processor.
28
  // These variable names match the possible return values of
29
  // FeedsPlugin::typeOf().
30
  protected $fetcher, $parser, $processor;
31

    
32
  // This array defines the variable names of the plugins above.
33
  protected $plugin_types = array('fetcher', 'parser', 'processor');
34

    
35
  /**
36
   * Instantiate class variables, initialize and configure
37
   * plugins.
38
   */
39
  protected function __construct($id) {
40
    parent::__construct($id);
41

    
42
    // Try to load information from database.
43
    $this->load();
44

    
45
    // Instantiate fetcher, parser and processor, set their configuration if
46
    // stored info is available.
47
    foreach ($this->plugin_types as $type) {
48
      $plugin = feeds_plugin($this->config[$type]['plugin_key'], $this->id);
49

    
50
      if (isset($this->config[$type]['config'])) {
51
        $plugin->setConfig($this->config[$type]['config']);
52
      }
53
      $this->$type = $plugin;
54
    }
55
  }
56

    
57
  /**
58
   * Remove items older than $time.
59
   *
60
   * @param $time
61
   *   All items older than REQUEST_TIME - $time will be deleted. If not
62
   *   given, internal processor settings will be used.
63
   *
64
   * @return
65
   *   FEEDS_BATCH_COMPLETE if the expiry process finished. A decimal between
66
   *   0.0 and 0.9 periodic if expiry is still in progress.
67
   *
68
   * @throws
69
   *   Throws Exception if an error occurs when expiring items.
70
   */
71
  public function expire($time = NULL) {
72
    return $this->processor->expire($time);
73
  }
74

    
75
  /**
76
   * Schedule all periodic tasks for this importer.
77
   */
78
  public function schedule() {
79
    $this->scheduleExpire();
80
  }
81

    
82
  /**
83
   * Schedule expiry of items.
84
   */
85
  public function scheduleExpire() {
86
    $job = array(
87
      'type' => $this->id,
88
      'period' => 0,
89
      'periodic' => TRUE,
90
    );
91
    if (FEEDS_EXPIRE_NEVER != $this->processor->expiryTime()) {
92
      $job['period'] = 3600;
93
      JobScheduler::get('feeds_importer_expire')->set($job);
94
    }
95
    else {
96
      JobScheduler::get('feeds_importer_expire')->remove($job);
97
    }
98
  }
99

    
100
  /**
101
   * Report how many items *should* be created on one page load by this
102
   * importer.
103
   *
104
   * Note:
105
   *
106
   * It depends on whether parser implements batching if this limit is actually
107
   * respected. Further, if no limit is reported it doesn't mean that the
108
   * number of items that can be created on one page load is actually without
109
   * limit.
110
   *
111
   * @return
112
   *   A positive number defining the number of items that can be created on
113
   *   one page load. 0 if this number is unlimited.
114
   */
115
  public function getLimit() {
116
    return $this->processor->getLimit();
117
  }
118

    
119
  /**
120
   * Save configuration.
121
   */
122
  public function save() {
123
    $save = new stdClass();
124
    $save->id = $this->id;
125
    $save->config = $this->getConfig();
126

    
127
    if ($config = db_query("SELECT config FROM {feeds_importer} WHERE id = :id", array(':id' => $this->id))->fetchField()) {
128
      drupal_write_record('feeds_importer', $save, 'id');
129
      // Only rebuild menu if content_type has changed. Don't worry about
130
      // rebuilding menus when creating a new importer since it will default
131
      // to the standalone page.
132
      $config = unserialize($config);
133
      if ($config['content_type'] != $save->config['content_type']) {
134
        variable_set('menu_rebuild_needed', TRUE);
135
      }
136
    }
137
    else {
138
      drupal_write_record('feeds_importer', $save);
139
    }
140
  }
141

    
142
  /**
143
   * Load configuration and unpack.
144
   */
145
  public function load() {
146
    ctools_include('export');
147
    if ($config = ctools_export_load_object('feeds_importer', 'conditions', array('id' => $this->id))) {
148
      $config = array_shift($config);
149
      $this->export_type = $config->export_type;
150
      $this->disabled = isset($config->disabled) ? $config->disabled : FALSE;
151
      $this->config = $config->config;
152
      return TRUE;
153
    }
154
    return FALSE;
155
  }
156

    
157
  /**
158
   * Delete configuration. Removes configuration information
159
   * from database, does not delete configuration itself.
160
   */
161
  public function delete() {
162
    db_delete('feeds_importer')
163
      ->condition('id', $this->id)
164
      ->execute();
165
    $job = array(
166
      'type' => $this->id,
167
      'id' => 0,
168
    );
169
    if ($this->export_type & EXPORT_IN_CODE) {
170
      feeds_reschedule($this->id);
171
    }
172
    else {
173
      JobScheduler::get('feeds_importer_expire')->remove($job);
174
    }
175
  }
176

    
177
  /**
178
   * Set plugin.
179
   *
180
   * @param $plugin_key
181
   *   A fetcher, parser or processor plugin.
182
   *
183
   * @todo Error handling, handle setting to the same plugin.
184
   */
185
  public function setPlugin($plugin_key) {
186
    // $plugin_type can be either 'fetcher', 'parser' or 'processor'
187
    if ($plugin_type = FeedsPlugin::typeOf($plugin_key)) {
188
      if ($plugin = feeds_plugin($plugin_key, $this->id)) {
189
        // Unset existing plugin, switch to new plugin.
190
        unset($this->$plugin_type);
191
        $this->$plugin_type = $plugin;
192
        // Set configuration information, blow away any previous information on
193
        // this spot.
194
        $this->config[$plugin_type] = array('plugin_key' => $plugin_key);
195
      }
196
    }
197
  }
198

    
199
  /**
200
   * Copy a FeedsImporter configuration into this importer.
201
   *
202
   * @param FeedsImporter $importer
203
   *   The feeds importer object to copy from.
204
   */
205
   public function copy(FeedsConfigurable $configurable) {
206
     parent::copy($configurable);
207

    
208
     if ($configurable instanceof FeedsImporter) {
209
       // Instantiate new fetcher, parser and processor and initialize their
210
       // configurations.
211
       foreach ($this->plugin_types as $plugin_type) {
212
         $this->setPlugin($configurable->config[$plugin_type]['plugin_key']);
213
         $this->$plugin_type->setConfig($configurable->config[$plugin_type]['config']);
214
       }
215
     }
216
   }
217

    
218
  /**
219
   * Get configuration of this feed.
220
   */
221
  public function getConfig() {
222
    foreach (array('fetcher', 'parser', 'processor') as $type) {
223
      $this->config[$type]['config'] = $this->$type->getConfig();
224
    }
225
    return parent::getConfig();
226
  }
227

    
228
  /**
229
   * Return defaults for feed configuration.
230
   */
231
  public function configDefaults() {
232
    return array(
233
      'name' => '',
234
      'description' => '',
235
      'fetcher' => array(
236
        'plugin_key' => 'FeedsHTTPFetcher',
237
      ),
238
      'parser' => array(
239
        'plugin_key' => 'FeedsSyndicationParser',
240
      ),
241
      'processor' => array(
242
        'plugin_key' => 'FeedsNodeProcessor',
243
      ),
244
      'content_type' => '',
245
      'update' => 0,
246
      'import_period' => 1800, // Refresh every 30 minutes by default.
247
      'expire_period' => 3600, // Expire every hour by default, this is a hidden setting.
248
      'import_on_create' => TRUE, // Import on submission.
249
      'process_in_background' => FALSE,
250
    );
251
  }
252

    
253
  /**
254
   * Override parent::configForm().
255
   */
256
  public function configForm(&$form_state) {
257
    $config = $this->getConfig();
258
    $form = array();
259
    $form['name'] = array(
260
      '#type' => 'textfield',
261
      '#title' => t('Name'),
262
      '#description' => t('A human readable name of this importer.'),
263
      '#default_value' => $config['name'],
264
      '#required' => TRUE,
265
    );
266
    $form['description'] = array(
267
      '#type' => 'textfield',
268
      '#title' => t('Description'),
269
      '#description' => t('A description of this importer.'),
270
      '#default_value' => $config['description'],
271
    );
272
    $node_types = node_type_get_names();
273
    array_walk($node_types, 'check_plain');
274
    $form['content_type'] = array(
275
      '#type' => 'select',
276
      '#title' => t('Attach to content type'),
277
      '#description' => t('If "Use standalone form" is selected a source is imported by using a form under !import_form.
278
                           If a content type is selected a source is imported by creating a node of that content type.',
279
                           array('!import_form' => l(url('import', array('absolute' => TRUE)), 'import', array('attributes' => array('target' => '_new'))))),
280
      '#options' => array('' => t('Use standalone form')) + $node_types,
281
      '#default_value' => $config['content_type'],
282
    );
283
    $cron_required =  ' ' . l(t('Requires cron to be configured.'), 'http://drupal.org/cron', array('attributes' => array('target' => '_new')));
284
    $period = drupal_map_assoc(array(900, 1800, 3600, 10800, 21600, 43200, 86400, 259200, 604800, 2419200), 'format_interval');
285
    foreach ($period as &$p) {
286
      $p = t('Every !p', array('!p' => $p));
287
    }
288
    $period = array(
289
      FEEDS_SCHEDULE_NEVER => t('Off'),
290
      0 => t('As often as possible'),
291
    ) + $period;
292
    $form['import_period'] = array(
293
      '#type' => 'select',
294
      '#title' => t('Periodic import'),
295
      '#options' => $period,
296
      '#description' => t('Choose how often a source should be imported periodically.') . $cron_required,
297
      '#default_value' => $config['import_period'],
298
    );
299
    $form['import_on_create'] = array(
300
      '#type' => 'checkbox',
301
      '#title' => t('Import on submission'),
302
      '#description' => t('Check if import should be started at the moment a standalone form or node form is submitted.'),
303
      '#default_value' => $config['import_on_create'],
304
    );
305
    $form['process_in_background'] = array(
306
      '#type' => 'checkbox',
307
      '#title' => t('Process in background'),
308
      '#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,
309
      '#default_value' => $config['process_in_background'],
310
    );
311
    return $form;
312
  }
313

    
314
  /**
315
   * Reschedule if import period changes.
316
   */
317
  public function configFormSubmit(&$values) {
318
    if ($this->config['import_period'] != $values['import_period']) {
319
      feeds_reschedule($this->id);
320
    }
321
    parent::configFormSubmit($values);
322
  }
323
}
324

    
325
/**
326
 * Helper, see FeedsDataProcessor class.
327
 */
328
function feeds_format_expire($timestamp) {
329
  if ($timestamp == FEEDS_EXPIRE_NEVER) {
330
    return t('Never');
331
  }
332
  return t('after !time', array('!time' => format_interval($timestamp)));
333
}