Projet

Général

Profil

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

root / drupal7 / sites / all / modules / feeds / plugins / FeedsHTTPFetcher.inc @ ec2b0e7b

1
<?php
2

    
3
/**
4
 * @file
5
 * Home of the FeedsHTTPFetcher and related classes.
6
 */
7

    
8
feeds_include_library('PuSHSubscriber.inc', 'PuSHSubscriber');
9

    
10
/**
11
 * Result of FeedsHTTPFetcher::fetch().
12
 */
13
class FeedsHTTPFetcherResult extends FeedsFetcherResult {
14

    
15
  /**
16
   * The URL of the feed being fetched.
17
   *
18
   * @var string
19
   */
20
  protected $url;
21

    
22
  /**
23
   * The timeout in seconds to wait for a download.
24
   *
25
   * @var int
26
   */
27
  protected $timeout;
28

    
29
  /**
30
   * Whether to ignore SSL validation errors.
31
   *
32
   * @var bool
33
   */
34
  protected $acceptInvalidCert;
35

    
36
  /**
37
   * Whether to cache the HTTP result.
38
   *
39
   * @var bool
40
   */
41
  protected $cacheHttpResult;
42

    
43
  /**
44
   * Constructor.
45
   */
46
  public function __construct($url = NULL) {
47
    $this->url = $url;
48
  }
49

    
50
  /**
51
   * Overrides FeedsFetcherResult::getRaw().
52
   *
53
   * @throws FeedsHTTPRequestException
54
   *   In case the result code of the HTTP request is not in the 2xx series.
55
   */
56
  public function getRaw() {
57
    if ($this->rawExists()) {
58
      return parent::getRaw();
59
    }
60

    
61
    // Include HTTP functions.
62
    feeds_include_library('http_request.inc', 'http_request');
63

    
64
    // Try to fetch the data from a URL.
65
    $result = feeds_http_request($this->url, array(
66
      'accept_invalid_cert' => $this->acceptInvalidCert,
67
      'timeout' => $this->timeout,
68
      'cache_http_result' => $this->cacheHttpResult,
69
    ));
70
    http_request_check_result($this->url, $result);
71
    $this->raw = $result->data;
72

    
73
    return $this->sanitizeRawOptimized($this->raw);
74
  }
75

    
76
  public function getTimeout() {
77
    return $this->timeout;
78
  }
79

    
80
  public function setTimeout($timeout) {
81
    $this->timeout = $timeout;
82
  }
83

    
84
  /**
85
   * Sets the accept invalid certificates option.
86
   *
87
   * @param bool $accept_invalid_cert
88
   *   Whether to accept invalid certificates.
89
   */
90
  public function setAcceptInvalidCert($accept_invalid_cert) {
91
    $this->acceptInvalidCert = (bool) $accept_invalid_cert;
92
  }
93

    
94
  /**
95
   * Sets the cache HTTP results of request option.
96
   *
97
   * @param bool $cache_http_result
98
   *   Whether to cache the HTTP result.
99
   */
100
  public function setCacheHttpResult($cache_http_result) {
101
    $this->cacheHttpResult = (bool) $cache_http_result;
102
  }
103

    
104
}
105

    
106
/**
107
 * Fetches data via HTTP.
108
 */
109
class FeedsHTTPFetcher extends FeedsFetcher {
110

    
111
  /**
112
   * Implements FeedsFetcher::fetch().
113
   */
114
  public function fetch(FeedsSource $source) {
115
    $source_config = $source->getConfigFor($this);
116
    if ($this->config['use_pubsubhubbub'] && ($raw = $this->subscriber($source->feed_nid)->receive())) {
117
      return new FeedsFetcherResult($raw);
118
    }
119
    $fetcher_result = new FeedsHTTPFetcherResult($source_config['source']);
120
    // When request_timeout is empty, the global value is used.
121
    $fetcher_result->setTimeout($this->config['request_timeout']);
122
    $fetcher_result->setAcceptInvalidCert($this->config['accept_invalid_cert']);
123
    $fetcher_result->setCacheHttpResult($this->config['cache_http_result']);
124
    return $fetcher_result;
125
  }
126

    
127
  /**
128
   * Clear caches.
129
   */
130
  public function clear(FeedsSource $source) {
131
    $source_config = $source->getConfigFor($this);
132
    $url = $source_config['source'];
133
    feeds_include_library('http_request.inc', 'http_request');
134
    http_request_clear_cache($url);
135
  }
136

    
137
  /**
138
   * Implements FeedsFetcher::request().
139
   */
140
  public function request($feed_nid = 0) {
141
    feeds_dbg($_GET);
142
    @feeds_dbg(file_get_contents('php://input'));
143
    // A subscription verification has been sent, verify.
144
    if (isset($_GET['hub_challenge'])) {
145
      $this->subscriber($feed_nid)->verifyRequest();
146
    }
147
    // No subscription notification has ben sent, we are being notified.
148
    else {
149
      $source = feeds_source($this->id, $feed_nid);
150
      try {
151
        $source->existing()->import();
152
      }
153
      catch (Exception $e) {
154
        // In case of an error, respond with a 503 Service (temporary) unavailable.
155
        $source->log('import', 'An exception occurred: %exception', array('%exception' => $e->getMessage()), WATCHDOG_ERROR);
156
        header('HTTP/1.1 503 "Not Found"', NULL, 503);
157
        drupal_exit();
158
      }
159
    }
160
    // Will generate the default 200 response.
161
    header('HTTP/1.1 200 "OK"', NULL, 200);
162
    drupal_exit();
163
  }
164

    
165
  /**
166
   * Override parent::configDefaults().
167
   */
168
  public function configDefaults() {
169
    return array(
170
      'auto_detect_feeds' => FALSE,
171
      'use_pubsubhubbub' => FALSE,
172
      'designated_hub' => '',
173
      'request_timeout' => NULL,
174
      'auto_scheme' => 'http',
175
      'accept_invalid_cert' => FALSE,
176
      'cache_http_result' => TRUE,
177
    ) + parent::configDefaults();
178
  }
179

    
180
  /**
181
   * Override parent::configForm().
182
   */
183
  public function configForm(&$form_state) {
184
    $form = array();
185
    $form['auto_detect_feeds'] = array(
186
      '#type' => 'checkbox',
187
      '#title' => t('Auto detect feeds'),
188
      '#description' => t('If the supplied URL does not point to a feed but an HTML document, attempt to extract a feed URL from the document.'),
189
      '#default_value' => $this->config['auto_detect_feeds'],
190
    );
191
    $form['use_pubsubhubbub'] = array(
192
      '#type' => 'checkbox',
193
      '#title' => t('Use PubSubHubbub'),
194
      '#description' => t('Attempt to use a <a href="http://en.wikipedia.org/wiki/PubSubHubbub">PubSubHubbub</a> subscription if available.'),
195
      '#default_value' => $this->config['use_pubsubhubbub'],
196
    );
197
    $form['advanced'] = array(
198
      '#title' => t('Advanced settings'),
199
      '#type' => 'fieldset',
200
      '#collapsible' => TRUE,
201
      '#collapsed' => TRUE,
202
    );
203
    $form['advanced']['auto_scheme'] = array(
204
      '#type' => 'textfield',
205
      '#title' => t('Automatically add scheme'),
206
      '#description' => t('If the supplied URL does not contain the scheme, use this one automatically. Keep empty to force the user to input the scheme.'),
207
      '#default_value' => $this->config['auto_scheme'],
208
    );
209
    $form['advanced']['designated_hub'] = array(
210
      '#type' => 'textfield',
211
      '#title' => t('Designated hub'),
212
      '#description' => t('Enter the URL of a designated PubSubHubbub hub (e. g. superfeedr.com). If given, this hub will be used instead of the hub specified in the actual feed.'),
213
      '#default_value' => $this->config['designated_hub'],
214
      '#states' => array(
215
        'visible' => array(':input[name="use_pubsubhubbub"]' => array('checked' => TRUE)),
216
      ),
217
    );
218
    // Per importer override of global http request timeout setting.
219
    $form['advanced']['request_timeout'] = array(
220
      '#type' => 'textfield',
221
      '#title' => t('Request timeout'),
222
      '#description' => t('Timeout in seconds to wait for an HTTP get request to finish.</br>' .
223
                         '<b>Note:</b> this setting will override the global setting.</br>' .
224
                         'When left empty, the global value is used.'),
225
      '#default_value' => $this->config['request_timeout'],
226
      '#element_validate' => array('element_validate_integer_positive'),
227
      '#maxlength' => 3,
228
      '#size'=> 30,
229
    );
230
    $form['advanced']['accept_invalid_cert'] = array(
231
      '#type' => 'checkbox',
232
      '#title' => t('Accept invalid SSL certificates'),
233
      '#description' => t('<strong>IMPORTANT:</strong> This setting will force cURL to completely ignore all SSL errors. This is a <strong>major security risk</strong> and should only be used during development.'),
234
      '#default_value' => $this->config['accept_invalid_cert'],
235
    );
236
    $form['advanced']['cache_http_result'] = array(
237
      '#type' => 'checkbox',
238
      '#title' => t('Cache HTTP result of request'),
239
      '#description' => '<p>' . t('Disabling this cache means that the downloaded source will not be cached (for example: on the file system or on memcache), but will be redownloaded on every feeds import attempt. This can be helpful if the source to download is dynamically generated and will always be different, or if it is very large (50 MB+) which would cost additional webspace.') . '</p><p>' . t("If you're having issues with Feeds not processing changes from the source file, or if you are experiencing caching issues, you can disable the caching of this feeds content.") . '</p>',
240
      '#default_value' => $this->config['cache_http_result'],
241
    );
242

    
243
    return $form;
244
  }
245

    
246
  /**
247
   * Expose source form.
248
   */
249
  public function sourceForm($source_config) {
250
    $form = array();
251
    $form['source'] = array(
252
      '#type' => 'textfield',
253
      '#title' => t('URL'),
254
      '#description' => t('Enter a feed URL.'),
255
      '#default_value' => isset($source_config['source']) ? $source_config['source'] : '',
256
      '#maxlength' => NULL,
257
      '#required' => TRUE,
258
    );
259
    return $form;
260
  }
261

    
262
  /**
263
   * Override parent::sourceFormValidate().
264
   */
265
  public function sourceFormValidate(&$values) {
266
    $values['source'] = trim($values['source']);
267

    
268
    // Keep a copy for error messages.
269
    $original_url = $values['source'];
270

    
271
    $parts = parse_url($values['source']);
272
    if (empty($parts['scheme']) && $this->config['auto_scheme']) {
273
      $values['source'] = $this->config['auto_scheme'] . '://' . $values['source'];
274
    }
275

    
276
    if (!feeds_valid_url($values['source'], TRUE)) {
277
      $form_key = 'feeds][' . get_class($this) . '][source';
278
      form_set_error($form_key, t('The URL %source is invalid.', array('%source' => $original_url)));
279
    }
280
    elseif ($this->config['auto_detect_feeds']) {
281
      feeds_include_library('http_request.inc', 'http_request');
282
      $url = http_request_get_common_syndication($values['source'], array(
283
        'accept_invalid_cert' => $this->config['accept_invalid_cert'],
284
      ));
285
      if ($url) {
286
        $values['source'] = $url;
287
      }
288
    }
289
  }
290

    
291
  /**
292
   * Override sourceSave() - subscribe to hub.
293
   */
294
  public function sourceSave(FeedsSource $source) {
295
    if ($this->config['use_pubsubhubbub']) {
296
      // If this is a feeds node we want to delay the subscription to
297
      // feeds_exit() to avoid transaction race conditions.
298
      if ($source->feed_nid) {
299
        $job = array('fetcher' => $this, 'source' => $source);
300
        feeds_set_subscription_job($job);
301
      }
302
      else {
303
        $this->subscribe($source);
304
      }
305
    }
306
  }
307

    
308
  /**
309
   * Override sourceDelete() - unsubscribe from hub.
310
   */
311
  public function sourceDelete(FeedsSource $source) {
312
    if ($this->config['use_pubsubhubbub']) {
313
      // If we're in a feed node, queue the unsubscribe,
314
      // else process immediately.
315
      if ($source->feed_nid) {
316
        $job = array(
317
          'type' => $source->id,
318
          'id' => $source->feed_nid,
319
          'period' => 0,
320
          'periodic' => FALSE,
321
        );
322
        JobScheduler::get('feeds_push_unsubscribe')->set($job);
323
      }
324
      else {
325
        $this->unsubscribe($source);
326
      }
327
    }
328
  }
329

    
330
  /**
331
   * Implement FeedsFetcher::subscribe() - subscribe to hub.
332
   */
333
  public function subscribe(FeedsSource $source) {
334
    $source_config = $source->getConfigFor($this);
335
    $this->subscriber($source->feed_nid)->subscribe($source_config['source'], url($this->path($source->feed_nid), array('absolute' => TRUE)), valid_url($this->config['designated_hub']) ? $this->config['designated_hub'] : '');
336
  }
337

    
338
  /**
339
   * Implement FeedsFetcher::unsubscribe() - unsubscribe from hub.
340
   */
341
  public function unsubscribe(FeedsSource $source) {
342
    $source_config = $source->getConfigFor($this);
343
    $this->subscriber($source->feed_nid)->unsubscribe($source_config['source'], url($this->path($source->feed_nid), array('absolute' => TRUE)));
344
  }
345

    
346
  /**
347
   * Implement FeedsFetcher::importPeriod().
348
   */
349
  public function importPeriod(FeedsSource $source) {
350
    if ($this->subscriber($source->feed_nid)->subscribed()) {
351
      return 259200; // Delay for three days if there is a successful subscription.
352
    }
353
  }
354

    
355
  /**
356
   * Convenience method for instantiating a subscriber object.
357
   */
358
  protected function subscriber($subscriber_id) {
359
    return PushSubscriber::instance($this->id, $subscriber_id, 'PuSHSubscription', PuSHEnvironment::instance());
360
  }
361
}
362

    
363
/**
364
 * Implement a PuSHSubscriptionInterface.
365
 */
366
class PuSHSubscription implements PuSHSubscriptionInterface {
367
  public $domain;
368
  public $subscriber_id;
369
  public $hub;
370
  public $topic;
371
  public $status;
372
  public $secret;
373
  public $post_fields;
374
  public $timestamp;
375

    
376
  /**
377
   * Load a subscription.
378
   */
379
  public static function load($domain, $subscriber_id) {
380
    if ($v = db_query("SELECT * FROM {feeds_push_subscriptions} WHERE domain = :domain AND subscriber_id = :sid", array(':domain' => $domain, ':sid' => $subscriber_id))->fetchAssoc()) {
381
      $v['post_fields'] = unserialize($v['post_fields']);
382
      return new PuSHSubscription($v['domain'], $v['subscriber_id'], $v['hub'], $v['topic'], $v['secret'], $v['status'], $v['post_fields'], $v['timestamp']);
383
    }
384
  }
385

    
386
  /**
387
   * Create a subscription.
388
   */
389
  public function __construct($domain, $subscriber_id, $hub, $topic, $secret, $status = '', $post_fields = '') {
390
    $this->domain = $domain;
391
    $this->subscriber_id = $subscriber_id;
392
    $this->hub = $hub;
393
    $this->topic = $topic;
394
    $this->status = $status;
395
    $this->secret = $secret;
396
    $this->post_fields = $post_fields;
397
  }
398

    
399
  /**
400
   * Save a subscription.
401
   */
402
  public function save() {
403
    $this->timestamp = time();
404
    $this->delete($this->domain, $this->subscriber_id);
405
    drupal_write_record('feeds_push_subscriptions', $this);
406
  }
407

    
408
  /**
409
   * Delete a subscription.
410
   */
411
  public function delete() {
412
    db_delete('feeds_push_subscriptions')
413
      ->condition('domain', $this->domain)
414
      ->condition('subscriber_id', $this->subscriber_id)
415
      ->execute();
416
  }
417
}
418

    
419
/**
420
 * Provide environmental functions to the PuSHSubscriber library.
421
 */
422
class PuSHEnvironment implements PuSHSubscriberEnvironmentInterface {
423
  /**
424
   * Singleton.
425
   */
426
  public static function instance() {
427
    static $env;
428
    if (empty($env)) {
429
      $env = new PuSHEnvironment();
430
    }
431
    return $env;
432
  }
433

    
434
  /**
435
   * Implements PuSHSubscriberEnvironmentInterface::msg().
436
   */
437
  public function msg($msg, $level = 'status') {
438
    drupal_set_message(check_plain($msg), $level);
439
  }
440

    
441
  /**
442
   * Implements PuSHSubscriberEnvironmentInterface::log().
443
   */
444
  public function log($msg, $level = 'status') {
445
    switch ($level) {
446
      case 'error':
447
        $severity = WATCHDOG_ERROR;
448
        break;
449
      case 'warning':
450
        $severity = WATCHDOG_WARNING;
451
        break;
452
      default:
453
        $severity = WATCHDOG_NOTICE;
454
        break;
455
    }
456
    feeds_dbg($msg);
457
    watchdog('FeedsHTTPFetcher', $msg, array(), $severity);
458
  }
459
}