Projet

Général

Profil

Paste
Télécharger (14,1 ko) Statistiques
| Branche: | Révision:

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

1
<?php
2

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

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

    
29
  /**
30
   * Every feed has a fetcher, a parser and a processor.
31
   *
32
   * These variable names match the possible return values of
33
   * FeedsPlugin::typeOf().
34
   */
35

    
36
  /**
37
   * The selected fetcher for this importer.
38
   *
39
   * @var FeedsFetcher|null
40
   */
41
  protected $fetcher;
42

    
43
  /**
44
   * The selected parser for this importer.
45
   *
46
   * @var FeedsParser|null
47
   */
48
  protected $parser;
49

    
50
  /**
51
   * The selected processor for this importer.
52
   *
53
   * @var FeedsProcessor|null
54
   */
55
  protected $processor;
56

    
57
  /**
58
   * This array defines the variable names of the plugins above.
59
   *
60
   * @var string[]
61
   */
62
  protected $plugin_types = array('fetcher', 'parser', 'processor');
63

    
64
  /**
65
   * Instantiate class variables, initialize and configure plugins.
66
   *
67
   * @param string $id
68
   *   The importer ID.
69
   */
70
  protected function __construct($id) {
71
    parent::__construct($id);
72

    
73
    // Try to load information from database.
74
    $this->load();
75

    
76
    // Instantiate fetcher, parser and processor, set their configuration if
77
    // stored info is available.
78
    foreach ($this->plugin_types as $type) {
79
      $plugin = feeds_plugin($this->config[$type]['plugin_key'], $this->id);
80

    
81
      if (isset($this->config[$type]['config'])) {
82
        $plugin->setConfig($this->config[$type]['config']);
83
      }
84
      $this->$type = $plugin;
85
    }
86
  }
87

    
88
  /**
89
   * Reports how many items *should* be created on one request by this importer.
90
   *
91
   * Note:
92
   *
93
   * It depends on whether parser implements batching if this limit is actually
94
   * respected. Further, if no limit is reported it doesn't mean that the
95
   * number of items that can be created on one page load is actually without
96
   * limit.
97
   *
98
   * @return int
99
   *   A positive number defining the number of items that can be created on
100
   *   one page load. 0 if this number is unlimited.
101
   */
102
  public function getLimit() {
103
    return $this->processor->getLimit();
104
  }
105

    
106
  /**
107
   * Save configuration.
108
   */
109
  public function save() {
110
    $save = new stdClass();
111
    $save->id = $this->id;
112
    $save->config = $this->getConfig();
113

    
114
    if ($config = db_query("SELECT config FROM {feeds_importer} WHERE id = :id", array(':id' => $this->id))->fetchField()) {
115
      drupal_write_record('feeds_importer', $save, 'id');
116
      // Only rebuild menu if content_type has changed. Don't worry about
117
      // rebuilding menus when creating a new importer since it will default
118
      // to the standalone page.
119
      $config = unserialize($config);
120
      if ($config['content_type'] != $save->config['content_type']) {
121
        variable_set('menu_rebuild_needed', TRUE);
122
      }
123
    }
124
    else {
125
      drupal_write_record('feeds_importer', $save);
126
    }
127
  }
128

    
129
  /**
130
   * Load configuration and unpack.
131
   */
132
  public function load() {
133
    ctools_include('export');
134
    if ($config = ctools_export_load_object('feeds_importer', 'conditions', array('id' => $this->id))) {
135
      $config = array_shift($config);
136
      $this->export_type = $config->export_type;
137
      $this->disabled = isset($config->disabled) ? $config->disabled : FALSE;
138
      $this->config = $config->config;
139
      return TRUE;
140
    }
141
    return FALSE;
142
  }
143

    
144
  /**
145
   * Deletes configuration.
146
   *
147
   * Removes configuration information from database, does not delete
148
   * configuration itself.
149
   */
150
  public function delete() {
151
    db_delete('feeds_importer')
152
      ->condition('id', $this->id)
153
      ->execute();
154

    
155
    feeds_reschedule($this->id);
156
  }
157

    
158
  /**
159
   * Set plugin.
160
   *
161
   * @param string $plugin_key
162
   *   The name of a fetcher, parser or processor plugin.
163
   *
164
   * @todo Error handling, handle setting to the same plugin.
165
   */
166
  public function setPlugin($plugin_key) {
167
    // $plugin_type can be either 'fetcher', 'parser' or 'processor'.
168
    if ($plugin_type = FeedsPlugin::typeOf($plugin_key)) {
169
      if ($plugin = feeds_plugin($plugin_key, $this->id)) {
170
        // Unset existing plugin, switch to new plugin.
171
        unset($this->$plugin_type);
172
        $this->$plugin_type = $plugin;
173
        // Set configuration information, blow away any previous information on
174
        // this spot.
175
        $this->config[$plugin_type] = array('plugin_key' => $plugin_key);
176
      }
177
    }
178
  }
179

    
180
  /**
181
   * Copy a FeedsImporter configuration into this importer.
182
   *
183
   * @param FeedsConfigurable $configurable
184
   *   The feeds importer object to copy from.
185
   */
186
  public function copy(FeedsConfigurable $configurable) {
187
    parent::copy($configurable);
188

    
189
    if ($configurable instanceof FeedsImporter) {
190
      // Instantiate new fetcher, parser and processor and initialize their
191
      // configurations.
192
      foreach ($this->plugin_types as $plugin_type) {
193
        $this->setPlugin($configurable->config[$plugin_type]['plugin_key']);
194
        $this->$plugin_type->setConfig($configurable->config[$plugin_type]['config']);
195
      }
196
    }
197
  }
198

    
199
  /**
200
   * Get configuration of this feed.
201
   */
202
  public function getConfig() {
203
    foreach (array('fetcher', 'parser', 'processor') as $type) {
204
      $this->config[$type]['config'] = $this->$type->getConfig();
205
    }
206
    return parent::getConfig();
207
  }
208

    
209
  /**
210
   * Validates the configuration.
211
   */
212
  public function validateConfig() {
213
    $errors = parent::validateConfig();
214
    $config = $this->getConfig();
215

    
216
    // Check if "Attach to content type" setting is the same as content type on
217
    // node processor.
218
    if ($this->processor instanceof FeedsNodeProcessor) {
219
      $processor_config = $this->processor->getConfig();
220
      if (!empty($config['content_type']) && $processor_config['bundle'] == $config['content_type']) {
221
        $message = t('The importer is attached to the same content type as the content type selected on the node processor. Unless you have a very advanced use case, these two should never be the same.');
222
        if (!module_exists('feeds_news')) {
223
          $message .= ' ' . t('Enable the Feeds News module for an example of how the "@content_type" setting can be used.', array(
224
            '@content_type' => t('Attach to content type'),
225
          ));
226
        }
227
        elseif ($this->id != 'feed') {
228
          $message .= ' ' . t('See the importer !importer_name (provided by the Feeds News module) for an example of how the "@content_type" setting can be used.', array(
229
            '!importer_name' => l(check_plain(feeds_importer('feed')->config['name']), 'admin/structure/feeds/feed'),
230
            '@content_type' => t('Attach to content type'),
231
          ));
232
        }
233

    
234
        $errors[] = $message;
235
      }
236
    }
237

    
238
    // Check for a combination of incompatible settings.
239
    if (!$config['import_on_create'] && empty($config['content_type'])) {
240
      if ($config['import_period'] == FEEDS_SCHEDULE_NEVER) {
241
        $errors[] = t('"@import_period" and "@import_on_create" are both turned off and the importer is not attached to a content type. Unless you have alternative methods of running imports for this importer, Feeds will not import anything for this importer.', array(
242
          '@import_period' => t('Periodic import'),
243
          '@import_on_create' => t('Import on submission'),
244
        ));
245
      }
246
      elseif ($config['process_in_background']) {
247
        $errors[] = t('Since "@import_on_create" is turned off and the importer is not attached to a content type, the "@process_in_background" setting may have no effect. When submitting the standalone form with the "@import_on_create" setting turned off, the feed is only scheduled for periodic import.', array(
248
          '@import_on_create' => t('Import on submission'),
249
          '@process_in_background' => t('Process in background'),
250
        ));
251
      }
252
    }
253

    
254
    // Validate errors of each plugin as well.
255
    $plugin_types = array(
256
      'fetcher' => t('Fetcher'),
257
      'parser' => t('Parser'),
258
      'processor' => t('Processor'),
259
    );
260
    foreach ($plugin_types as $type => $plugin_label) {
261
      // Check if plugin exists.
262
      $plugin = feeds_plugin($this->config[$type]['plugin_key'], $this->id);
263
      if ($plugin instanceof FeedsMissingPlugin) {
264
        $errors[] = t('The plugin %plugin is unavailable.', array('%plugin' => $this->config[$type]['plugin_key']));
265
        continue;
266
      }
267

    
268
      $plugin_errors = $this->$type->validateConfig();
269
      foreach ($plugin_errors as $key => $error) {
270
        $errors[] = t('@plugin: !error', array(
271
          '@plugin' => $plugin_label,
272
          '!error' => $error,
273
        ));
274
      }
275
    }
276

    
277
    return $errors;
278
  }
279

    
280
  /**
281
   * Return defaults for feed configuration.
282
   */
283
  public function configDefaults() {
284
    return array(
285
      'name' => '',
286
      'description' => '',
287
      'fetcher' => array(
288
        'plugin_key' => 'FeedsHTTPFetcher',
289
      ),
290
      'parser' => array(
291
        'plugin_key' => 'FeedsSyndicationParser',
292
      ),
293
      'processor' => array(
294
        'plugin_key' => 'FeedsNodeProcessor',
295
      ),
296
      'content_type' => '',
297
      'update' => 0,
298
      // Refresh every 30 minutes by default.
299
      'import_period' => 1800,
300
      // Expire every hour by default, this is a hidden setting.
301
      'expire_period' => 3600,
302
      // Import on submission.
303
      'import_on_create' => TRUE,
304
      'process_in_background' => FALSE,
305
    ) + parent::configDefaults();
306
  }
307

    
308
  /**
309
   * Overrides parent::configForm().
310
   *
311
   * @param array $form_state
312
   *   The current state of the form.
313
   *
314
   * @return array
315
   *   The form definition.
316
   */
317
  public function configForm(&$form_state) {
318
    $config = $this->getConfig();
319
    $form = array();
320
    $form['name'] = array(
321
      '#type' => 'textfield',
322
      '#title' => t('Name'),
323
      '#description' => t('A human readable name of this importer.'),
324
      '#default_value' => $config['name'],
325
      '#required' => TRUE,
326
    );
327
    $form['description'] = array(
328
      '#type' => 'textfield',
329
      '#title' => t('Description'),
330
      '#description' => t('A description of this importer.'),
331
      '#default_value' => $config['description'],
332
    );
333
    $node_types = node_type_get_names();
334
    array_walk($node_types, 'check_plain');
335
    $form['content_type'] = array(
336
      '#type' => 'select',
337
      '#title' => t('Attach to content type'),
338
      '#description' => '<p>' . 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(
339
        '!import_form' => l(url('import', array('absolute' => TRUE)), 'import', array('attributes' => array('target' => '_new'))),
340
      )) . '</p>',
341
      '#options' => array('' => t('Use standalone form')) + $node_types,
342
      '#default_value' => $config['content_type'],
343
    );
344
    $cron_required = ' ' . l(t('Requires cron to be configured.'), 'http://drupal.org/cron', array('attributes' => array('target' => '_new')));
345
    $period = drupal_map_assoc(array(
346
      900,
347
      1800,
348
      3600,
349
      10800,
350
      21600,
351
      43200,
352
      86400,
353
      259200,
354
      604800,
355
      2419200,
356
    ), 'format_interval');
357
    foreach ($period as &$p) {
358
      $p = t('Every !p', array('!p' => $p));
359
    }
360
    $period = array(
361
      FEEDS_SCHEDULE_NEVER => t('Off'),
362
      0 => t('As often as possible'),
363
    ) + $period;
364
    $form['import_period'] = array(
365
      '#type' => 'select',
366
      '#title' => t('Periodic import'),
367
      '#options' => $period,
368
      '#description' => t('Choose how often a source should be imported periodically.') . $cron_required,
369
      '#default_value' => $config['import_period'],
370
    );
371
    $form['import_on_create'] = array(
372
      '#type' => 'checkbox',
373
      '#title' => t('Import on submission'),
374
      '#description' => t('Check if import should be started at the moment a standalone form or node form is submitted. If not checked and when using the standalone form, be sure to configure periodic import.'),
375
      '#default_value' => $config['import_on_create'],
376
    );
377
    $form['process_in_background'] = array(
378
      '#type' => 'checkbox',
379
      '#title' => t('Process in background'),
380
      '#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,
381
      '#default_value' => $config['process_in_background'],
382
    );
383
    return $form;
384
  }
385

    
386
  /**
387
   * Overrides parent::configFormSubmit().
388
   *
389
   * Reschedule if import period changes.
390
   *
391
   * @param array $values
392
   *   An array that contains the values entered by the user through configForm.
393
   */
394
  public function configFormSubmit(&$values) {
395
    if ($this->config['import_period'] != $values['import_period']) {
396
      feeds_reschedule($this->id);
397
    }
398
    parent::configFormSubmit($values);
399
  }
400

    
401
  /**
402
   * Implements FeedsConfigurable::dependencies().
403
   */
404
  public function dependencies() {
405
    $dependencies = parent::dependencies();
406
    foreach ($this->plugin_types as $plugin_type) {
407
      $dependencies = array_merge($dependencies, $this->$plugin_type->dependencies());
408
    }
409
    return $dependencies;
410
  }
411

    
412
}
413

    
414
/**
415
 * Formats a time interval.
416
 *
417
 * @param int $timestamp
418
 *   The length of the interval in seconds.
419
 *
420
 * @return string
421
 *   A translated string representation of the interval.
422
 */
423
function feeds_format_expire($timestamp) {
424
  if ($timestamp == FEEDS_EXPIRE_NEVER) {
425
    return t('Never');
426
  }
427
  return t('after !time', array('!time' => format_interval($timestamp)));
428
}