Projet

Général

Profil

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

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

1 85ad3d82 Assos Assos
<?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 41cc1b08 Assos Assos
15
  /**
16
   * The URL of the feed being fetched.
17
   *
18
   * @var string
19
   */
20 85ad3d82 Assos Assos
  protected $url;
21 41cc1b08 Assos Assos
22
  /**
23
   * The timeout in seconds to wait for a download.
24
   *
25
   * @var int
26
   */
27 85ad3d82 Assos Assos
  protected $timeout;
28
29 41cc1b08 Assos Assos
  /**
30
   * Whether to ignore SSL validation errors.
31
   *
32
   * @var bool
33
   */
34
  protected $acceptInvalidCert;
35
36 ec2b0e7b Assos Assos
  /**
37
   * Whether to cache the HTTP result.
38
   *
39
   * @var bool
40
   */
41
  protected $cacheHttpResult;
42
43 85ad3d82 Assos Assos
  /**
44
   * Constructor.
45
   */
46
  public function __construct($url = NULL) {
47
    $this->url = $url;
48
  }
49
50
  /**
51 ec2b0e7b Assos Assos
   * Overrides FeedsFetcherResult::getRaw().
52
   *
53
   * @throws FeedsHTTPRequestException
54
   *   In case the result code of the HTTP request is not in the 2xx series.
55 85ad3d82 Assos Assos
   */
56
  public function getRaw() {
57 ec2b0e7b Assos Assos
    if ($this->rawExists()) {
58
      return parent::getRaw();
59 85ad3d82 Assos Assos
    }
60 41cc1b08 Assos Assos
61 ec2b0e7b Assos Assos
    // 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 85ad3d82 Assos Assos
  }
75
76 ed9a13f1 Assos Assos
  /**
77
   * Returns the configured value for the request timeout option.
78
   *
79
   * @return int
80
   *   Timeout in seconds to wait for an HTTP get request to finish.
81
   */
82 85ad3d82 Assos Assos
  public function getTimeout() {
83
    return $this->timeout;
84
  }
85
86 ed9a13f1 Assos Assos
  /**
87
   * Sets the request timeout option.
88
   *
89
   * @param int $timeout
90
   *   Timeout in seconds to wait for an HTTP get request to finish.
91
   */
92 85ad3d82 Assos Assos
  public function setTimeout($timeout) {
93
    $this->timeout = $timeout;
94
  }
95 41cc1b08 Assos Assos
96
  /**
97
   * Sets the accept invalid certificates option.
98
   *
99
   * @param bool $accept_invalid_cert
100
   *   Whether to accept invalid certificates.
101
   */
102
  public function setAcceptInvalidCert($accept_invalid_cert) {
103
    $this->acceptInvalidCert = (bool) $accept_invalid_cert;
104
  }
105
106 ec2b0e7b Assos Assos
  /**
107
   * Sets the cache HTTP results of request option.
108
   *
109
   * @param bool $cache_http_result
110
   *   Whether to cache the HTTP result.
111
   */
112
  public function setCacheHttpResult($cache_http_result) {
113
    $this->cacheHttpResult = (bool) $cache_http_result;
114
  }
115
116 85ad3d82 Assos Assos
}
117
118
/**
119
 * Fetches data via HTTP.
120
 */
121
class FeedsHTTPFetcher extends FeedsFetcher {
122
123
  /**
124
   * Implements FeedsFetcher::fetch().
125
   */
126
  public function fetch(FeedsSource $source) {
127
    $source_config = $source->getConfigFor($this);
128
    if ($this->config['use_pubsubhubbub'] && ($raw = $this->subscriber($source->feed_nid)->receive())) {
129
      return new FeedsFetcherResult($raw);
130
    }
131
    $fetcher_result = new FeedsHTTPFetcherResult($source_config['source']);
132
    // When request_timeout is empty, the global value is used.
133
    $fetcher_result->setTimeout($this->config['request_timeout']);
134 41cc1b08 Assos Assos
    $fetcher_result->setAcceptInvalidCert($this->config['accept_invalid_cert']);
135 ec2b0e7b Assos Assos
    $fetcher_result->setCacheHttpResult($this->config['cache_http_result']);
136 85ad3d82 Assos Assos
    return $fetcher_result;
137
  }
138
139
  /**
140
   * Clear caches.
141
   */
142
  public function clear(FeedsSource $source) {
143
    $source_config = $source->getConfigFor($this);
144
    $url = $source_config['source'];
145
    feeds_include_library('http_request.inc', 'http_request');
146
    http_request_clear_cache($url);
147
  }
148
149
  /**
150
   * Implements FeedsFetcher::request().
151
   */
152
  public function request($feed_nid = 0) {
153
    feeds_dbg($_GET);
154
    @feeds_dbg(file_get_contents('php://input'));
155
    // A subscription verification has been sent, verify.
156
    if (isset($_GET['hub_challenge'])) {
157
      $this->subscriber($feed_nid)->verifyRequest();
158
    }
159
    // No subscription notification has ben sent, we are being notified.
160
    else {
161 ec2b0e7b Assos Assos
      $source = feeds_source($this->id, $feed_nid);
162 85ad3d82 Assos Assos
      try {
163 ec2b0e7b Assos Assos
        $source->existing()->import();
164 85ad3d82 Assos Assos
      }
165
      catch (Exception $e) {
166 ed9a13f1 Assos Assos
        // In case of an error, respond with a 503 Service (temporary)
167
        // unavailable.
168 ec2b0e7b Assos Assos
        $source->log('import', 'An exception occurred: %exception', array('%exception' => $e->getMessage()), WATCHDOG_ERROR);
169 85ad3d82 Assos Assos
        header('HTTP/1.1 503 "Not Found"', NULL, 503);
170
        drupal_exit();
171
      }
172
    }
173
    // Will generate the default 200 response.
174
    header('HTTP/1.1 200 "OK"', NULL, 200);
175
    drupal_exit();
176
  }
177
178
  /**
179
   * Override parent::configDefaults().
180
   */
181
  public function configDefaults() {
182
    return array(
183
      'auto_detect_feeds' => FALSE,
184
      'use_pubsubhubbub' => FALSE,
185
      'designated_hub' => '',
186
      'request_timeout' => NULL,
187 41cc1b08 Assos Assos
      'auto_scheme' => 'http',
188
      'accept_invalid_cert' => FALSE,
189 ec2b0e7b Assos Assos
      'cache_http_result' => TRUE,
190
    ) + parent::configDefaults();
191 85ad3d82 Assos Assos
  }
192
193
  /**
194
   * Override parent::configForm().
195
   */
196
  public function configForm(&$form_state) {
197
    $form = array();
198
    $form['auto_detect_feeds'] = array(
199
      '#type' => 'checkbox',
200
      '#title' => t('Auto detect feeds'),
201
      '#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.'),
202
      '#default_value' => $this->config['auto_detect_feeds'],
203
    );
204
    $form['use_pubsubhubbub'] = array(
205
      '#type' => 'checkbox',
206
      '#title' => t('Use PubSubHubbub'),
207
      '#description' => t('Attempt to use a <a href="http://en.wikipedia.org/wiki/PubSubHubbub">PubSubHubbub</a> subscription if available.'),
208
      '#default_value' => $this->config['use_pubsubhubbub'],
209
    );
210 41cc1b08 Assos Assos
    $form['advanced'] = array(
211
      '#title' => t('Advanced settings'),
212
      '#type' => 'fieldset',
213
      '#collapsible' => TRUE,
214
      '#collapsed' => TRUE,
215
    );
216
    $form['advanced']['auto_scheme'] = array(
217
      '#type' => 'textfield',
218
      '#title' => t('Automatically add scheme'),
219
      '#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.'),
220
      '#default_value' => $this->config['auto_scheme'],
221
    );
222
    $form['advanced']['designated_hub'] = array(
223 85ad3d82 Assos Assos
      '#type' => 'textfield',
224
      '#title' => t('Designated hub'),
225
      '#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.'),
226
      '#default_value' => $this->config['designated_hub'],
227 41cc1b08 Assos Assos
      '#states' => array(
228
        'visible' => array(':input[name="use_pubsubhubbub"]' => array('checked' => TRUE)),
229 85ad3d82 Assos Assos
      ),
230
    );
231 41cc1b08 Assos Assos
    // Per importer override of global http request timeout setting.
232
    $form['advanced']['request_timeout'] = array(
233
      '#type' => 'textfield',
234
      '#title' => t('Request timeout'),
235 ed9a13f1 Assos Assos
      '#description' => t('Timeout in seconds to wait for an HTTP get request to finish.') . '<br />' . t('<strong>Note:</strong> if left empty, the global timeout setting will be used, which is @timeout seconds. You can set the global timeout setting by setting the variable "@variable".', array(
236
        '@timeout' => variable_get('http_request_timeout', 30),
237
        '@variable' => 'http_request_timeout',
238
      )),
239 41cc1b08 Assos Assos
      '#default_value' => $this->config['request_timeout'],
240
      '#element_validate' => array('element_validate_integer_positive'),
241
      '#maxlength' => 3,
242 ed9a13f1 Assos Assos
      '#size' => 30,
243 41cc1b08 Assos Assos
    );
244
    $form['advanced']['accept_invalid_cert'] = array(
245
      '#type' => 'checkbox',
246
      '#title' => t('Accept invalid SSL certificates'),
247
      '#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.'),
248
      '#default_value' => $this->config['accept_invalid_cert'],
249
    );
250 ec2b0e7b Assos Assos
    $form['advanced']['cache_http_result'] = array(
251
      '#type' => 'checkbox',
252
      '#title' => t('Cache HTTP result of request'),
253
      '#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>',
254
      '#default_value' => $this->config['cache_http_result'],
255
    );
256 41cc1b08 Assos Assos
257 85ad3d82 Assos Assos
    return $form;
258
  }
259
260
  /**
261
   * Expose source form.
262
   */
263
  public function sourceForm($source_config) {
264
    $form = array();
265
    $form['source'] = array(
266
      '#type' => 'textfield',
267
      '#title' => t('URL'),
268
      '#description' => t('Enter a feed URL.'),
269
      '#default_value' => isset($source_config['source']) ? $source_config['source'] : '',
270
      '#maxlength' => NULL,
271
      '#required' => TRUE,
272
    );
273
    return $form;
274
  }
275
276
  /**
277
   * Override parent::sourceFormValidate().
278
   */
279
  public function sourceFormValidate(&$values) {
280
    $values['source'] = trim($values['source']);
281
282 41cc1b08 Assos Assos
    // Keep a copy for error messages.
283
    $original_url = $values['source'];
284
285
    $parts = parse_url($values['source']);
286
    if (empty($parts['scheme']) && $this->config['auto_scheme']) {
287
      $values['source'] = $this->config['auto_scheme'] . '://' . $values['source'];
288
    }
289
290 85ad3d82 Assos Assos
    if (!feeds_valid_url($values['source'], TRUE)) {
291
      $form_key = 'feeds][' . get_class($this) . '][source';
292 41cc1b08 Assos Assos
      form_set_error($form_key, t('The URL %source is invalid.', array('%source' => $original_url)));
293 85ad3d82 Assos Assos
    }
294
    elseif ($this->config['auto_detect_feeds']) {
295
      feeds_include_library('http_request.inc', 'http_request');
296 41cc1b08 Assos Assos
      $url = http_request_get_common_syndication($values['source'], array(
297
        'accept_invalid_cert' => $this->config['accept_invalid_cert'],
298
      ));
299
      if ($url) {
300 85ad3d82 Assos Assos
        $values['source'] = $url;
301
      }
302
    }
303
  }
304
305
  /**
306
   * Override sourceSave() - subscribe to hub.
307
   */
308
  public function sourceSave(FeedsSource $source) {
309
    if ($this->config['use_pubsubhubbub']) {
310
      // If this is a feeds node we want to delay the subscription to
311
      // feeds_exit() to avoid transaction race conditions.
312
      if ($source->feed_nid) {
313
        $job = array('fetcher' => $this, 'source' => $source);
314
        feeds_set_subscription_job($job);
315
      }
316
      else {
317
        $this->subscribe($source);
318
      }
319
    }
320
  }
321
322
  /**
323
   * Override sourceDelete() - unsubscribe from hub.
324
   */
325
  public function sourceDelete(FeedsSource $source) {
326
    if ($this->config['use_pubsubhubbub']) {
327
      // If we're in a feed node, queue the unsubscribe,
328
      // else process immediately.
329
      if ($source->feed_nid) {
330
        $job = array(
331
          'type' => $source->id,
332
          'id' => $source->feed_nid,
333
          'period' => 0,
334
          'periodic' => FALSE,
335
        );
336
        JobScheduler::get('feeds_push_unsubscribe')->set($job);
337
      }
338
      else {
339
        $this->unsubscribe($source);
340
      }
341
    }
342
  }
343
344
  /**
345
   * Implement FeedsFetcher::subscribe() - subscribe to hub.
346
   */
347
  public function subscribe(FeedsSource $source) {
348
    $source_config = $source->getConfigFor($this);
349
    $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'] : '');
350
  }
351
352
  /**
353
   * Implement FeedsFetcher::unsubscribe() - unsubscribe from hub.
354
   */
355
  public function unsubscribe(FeedsSource $source) {
356
    $source_config = $source->getConfigFor($this);
357
    $this->subscriber($source->feed_nid)->unsubscribe($source_config['source'], url($this->path($source->feed_nid), array('absolute' => TRUE)));
358
  }
359
360
  /**
361
   * Implement FeedsFetcher::importPeriod().
362
   */
363
  public function importPeriod(FeedsSource $source) {
364
    if ($this->subscriber($source->feed_nid)->subscribed()) {
365 ed9a13f1 Assos Assos
      // Delay for three days if there is a successful subscription.
366
      return 259200;
367 85ad3d82 Assos Assos
    }
368
  }
369
370
  /**
371
   * Convenience method for instantiating a subscriber object.
372
   */
373
  protected function subscriber($subscriber_id) {
374
    return PushSubscriber::instance($this->id, $subscriber_id, 'PuSHSubscription', PuSHEnvironment::instance());
375
  }
376 ed9a13f1 Assos Assos
377 85ad3d82 Assos Assos
}
378
379
/**
380
 * Implement a PuSHSubscriptionInterface.
381
 */
382
class PuSHSubscription implements PuSHSubscriptionInterface {
383
  public $domain;
384
  public $subscriber_id;
385
  public $hub;
386
  public $topic;
387
  public $status;
388
  public $secret;
389
  public $post_fields;
390
  public $timestamp;
391
392
  /**
393
   * Load a subscription.
394
   */
395
  public static function load($domain, $subscriber_id) {
396
    if ($v = db_query("SELECT * FROM {feeds_push_subscriptions} WHERE domain = :domain AND subscriber_id = :sid", array(':domain' => $domain, ':sid' => $subscriber_id))->fetchAssoc()) {
397
      $v['post_fields'] = unserialize($v['post_fields']);
398
      return new PuSHSubscription($v['domain'], $v['subscriber_id'], $v['hub'], $v['topic'], $v['secret'], $v['status'], $v['post_fields'], $v['timestamp']);
399
    }
400
  }
401
402
  /**
403
   * Create a subscription.
404
   */
405
  public function __construct($domain, $subscriber_id, $hub, $topic, $secret, $status = '', $post_fields = '') {
406
    $this->domain = $domain;
407
    $this->subscriber_id = $subscriber_id;
408
    $this->hub = $hub;
409
    $this->topic = $topic;
410
    $this->status = $status;
411
    $this->secret = $secret;
412
    $this->post_fields = $post_fields;
413
  }
414
415
  /**
416
   * Save a subscription.
417
   */
418
  public function save() {
419
    $this->timestamp = time();
420
    $this->delete($this->domain, $this->subscriber_id);
421
    drupal_write_record('feeds_push_subscriptions', $this);
422
  }
423
424
  /**
425
   * Delete a subscription.
426
   */
427
  public function delete() {
428
    db_delete('feeds_push_subscriptions')
429
      ->condition('domain', $this->domain)
430
      ->condition('subscriber_id', $this->subscriber_id)
431
      ->execute();
432
  }
433 ed9a13f1 Assos Assos
434 85ad3d82 Assos Assos
}
435
436
/**
437
 * Provide environmental functions to the PuSHSubscriber library.
438
 */
439
class PuSHEnvironment implements PuSHSubscriberEnvironmentInterface {
440 ed9a13f1 Assos Assos
441 85ad3d82 Assos Assos
  /**
442
   * Singleton.
443
   */
444
  public static function instance() {
445
    static $env;
446
    if (empty($env)) {
447
      $env = new PuSHEnvironment();
448
    }
449
    return $env;
450
  }
451
452
  /**
453
   * Implements PuSHSubscriberEnvironmentInterface::msg().
454
   */
455
  public function msg($msg, $level = 'status') {
456
    drupal_set_message(check_plain($msg), $level);
457
  }
458
459
  /**
460
   * Implements PuSHSubscriberEnvironmentInterface::log().
461
   */
462
  public function log($msg, $level = 'status') {
463
    switch ($level) {
464
      case 'error':
465
        $severity = WATCHDOG_ERROR;
466
        break;
467 ed9a13f1 Assos Assos
468 85ad3d82 Assos Assos
      case 'warning':
469
        $severity = WATCHDOG_WARNING;
470
        break;
471 ed9a13f1 Assos Assos
472 85ad3d82 Assos Assos
      default:
473
        $severity = WATCHDOG_NOTICE;
474
        break;
475
    }
476
    feeds_dbg($msg);
477
    watchdog('FeedsHTTPFetcher', $msg, array(), $severity);
478
  }
479 ed9a13f1 Assos Assos
480 85ad3d82 Assos Assos
}