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
<?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
  /**
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
  public function getTimeout() {
83
    return $this->timeout;
84
  }
85

    
86
  /**
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
  public function setTimeout($timeout) {
93
    $this->timeout = $timeout;
94
  }
95

    
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
  /**
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
}
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
    $fetcher_result->setAcceptInvalidCert($this->config['accept_invalid_cert']);
135
    $fetcher_result->setCacheHttpResult($this->config['cache_http_result']);
136
    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
      $source = feeds_source($this->id, $feed_nid);
162
      try {
163
        $source->existing()->import();
164
      }
165
      catch (Exception $e) {
166
        // In case of an error, respond with a 503 Service (temporary)
167
        // unavailable.
168
        $source->log('import', 'An exception occurred: %exception', array('%exception' => $e->getMessage()), WATCHDOG_ERROR);
169
        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
      'auto_scheme' => 'http',
188
      'accept_invalid_cert' => FALSE,
189
      'cache_http_result' => TRUE,
190
    ) + parent::configDefaults();
191
  }
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
    $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
      '#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
      '#states' => array(
228
        'visible' => array(':input[name="use_pubsubhubbub"]' => array('checked' => TRUE)),
229
      ),
230
    );
231
    // Per importer override of global http request timeout setting.
232
    $form['advanced']['request_timeout'] = array(
233
      '#type' => 'textfield',
234
      '#title' => t('Request timeout'),
235
      '#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
      '#default_value' => $this->config['request_timeout'],
240
      '#element_validate' => array('element_validate_integer_positive'),
241
      '#maxlength' => 3,
242
      '#size' => 30,
243
    );
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
    $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

    
257
    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
    // 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
    if (!feeds_valid_url($values['source'], TRUE)) {
291
      $form_key = 'feeds][' . get_class($this) . '][source';
292
      form_set_error($form_key, t('The URL %source is invalid.', array('%source' => $original_url)));
293
    }
294
    elseif ($this->config['auto_detect_feeds']) {
295
      feeds_include_library('http_request.inc', 'http_request');
296
      $url = http_request_get_common_syndication($values['source'], array(
297
        'accept_invalid_cert' => $this->config['accept_invalid_cert'],
298
      ));
299
      if ($url) {
300
        $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
      // Delay for three days if there is a successful subscription.
366
      return 259200;
367
    }
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

    
377
}
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

    
434
}
435

    
436
/**
437
 * Provide environmental functions to the PuSHSubscriber library.
438
 */
439
class PuSHEnvironment implements PuSHSubscriberEnvironmentInterface {
440

    
441
  /**
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

    
468
      case 'warning':
469
        $severity = WATCHDOG_WARNING;
470
        break;
471

    
472
      default:
473
        $severity = WATCHDOG_NOTICE;
474
        break;
475
    }
476
    feeds_dbg($msg);
477
    watchdog('FeedsHTTPFetcher', $msg, array(), $severity);
478
  }
479

    
480
}