Projet

Général

Profil

Paste
Télécharger (12,6 ko) Statistiques
| Branche: | Révision:

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

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
   *
31
   * Whether to ignore SSL validation errors.
32
   *
33
   * @var bool
34
   */
35
  protected $acceptInvalidCert;
36

    
37
  /**
38
   * Constructor.
39
   */
40
  public function __construct($url = NULL) {
41
    $this->url = $url;
42
  }
43

    
44
  /**
45
   * Overrides FeedsFetcherResult::getRaw();
46
   */
47
  public function getRaw() {
48
    if (!isset($this->raw)) {
49
      feeds_include_library('http_request.inc', 'http_request');
50
      $result = http_request_get($this->url, NULL, NULL, $this->acceptInvalidCert, $this->timeout);
51
      if (!in_array($result->code, array(200, 201, 202, 203, 204, 205, 206))) {
52
        throw new Exception(t('Download of @url failed with code !code.', array('@url' => $this->url, '!code' => $result->code)));
53
      }
54
      $this->raw = $result->data;
55
    }
56

    
57
    return $this->sanitizeRaw($this->raw);
58
  }
59

    
60
  public function getTimeout() {
61
    return $this->timeout;
62
  }
63

    
64
  public function setTimeout($timeout) {
65
    $this->timeout = $timeout;
66
  }
67

    
68
  /**
69
   * Sets the accept invalid certificates option.
70
   *
71
   * @param bool $accept_invalid_cert
72
   *   Whether to accept invalid certificates.
73
   */
74
  public function setAcceptInvalidCert($accept_invalid_cert) {
75
    $this->acceptInvalidCert = (bool) $accept_invalid_cert;
76
  }
77

    
78
}
79

    
80
/**
81
 * Fetches data via HTTP.
82
 */
83
class FeedsHTTPFetcher extends FeedsFetcher {
84

    
85
  /**
86
   * Implements FeedsFetcher::fetch().
87
   */
88
  public function fetch(FeedsSource $source) {
89
    $source_config = $source->getConfigFor($this);
90
    if ($this->config['use_pubsubhubbub'] && ($raw = $this->subscriber($source->feed_nid)->receive())) {
91
      return new FeedsFetcherResult($raw);
92
    }
93
    $fetcher_result = new FeedsHTTPFetcherResult($source_config['source']);
94
    // When request_timeout is empty, the global value is used.
95
    $fetcher_result->setTimeout($this->config['request_timeout']);
96
    $fetcher_result->setAcceptInvalidCert($this->config['accept_invalid_cert']);
97
    return $fetcher_result;
98
  }
99

    
100
  /**
101
   * Clear caches.
102
   */
103
  public function clear(FeedsSource $source) {
104
    $source_config = $source->getConfigFor($this);
105
    $url = $source_config['source'];
106
    feeds_include_library('http_request.inc', 'http_request');
107
    http_request_clear_cache($url);
108
  }
109

    
110
  /**
111
   * Implements FeedsFetcher::request().
112
   */
113
  public function request($feed_nid = 0) {
114
    feeds_dbg($_GET);
115
    @feeds_dbg(file_get_contents('php://input'));
116
    // A subscription verification has been sent, verify.
117
    if (isset($_GET['hub_challenge'])) {
118
      $this->subscriber($feed_nid)->verifyRequest();
119
    }
120
    // No subscription notification has ben sent, we are being notified.
121
    else {
122
      try {
123
        feeds_source($this->id, $feed_nid)->existing()->import();
124
      }
125
      catch (Exception $e) {
126
        // In case of an error, respond with a 503 Service (temporary) unavailable.
127
        header('HTTP/1.1 503 "Not Found"', NULL, 503);
128
        drupal_exit();
129
      }
130
    }
131
    // Will generate the default 200 response.
132
    header('HTTP/1.1 200 "OK"', NULL, 200);
133
    drupal_exit();
134
  }
135

    
136
  /**
137
   * Override parent::configDefaults().
138
   */
139
  public function configDefaults() {
140
    return array(
141
      'auto_detect_feeds' => FALSE,
142
      'use_pubsubhubbub' => FALSE,
143
      'designated_hub' => '',
144
      'request_timeout' => NULL,
145
      'auto_scheme' => 'http',
146
      'accept_invalid_cert' => FALSE,
147
    );
148
  }
149

    
150
  /**
151
   * Override parent::configForm().
152
   */
153
  public function configForm(&$form_state) {
154
    $form = array();
155
    $form['auto_detect_feeds'] = array(
156
      '#type' => 'checkbox',
157
      '#title' => t('Auto detect feeds'),
158
      '#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.'),
159
      '#default_value' => $this->config['auto_detect_feeds'],
160
    );
161
    $form['use_pubsubhubbub'] = array(
162
      '#type' => 'checkbox',
163
      '#title' => t('Use PubSubHubbub'),
164
      '#description' => t('Attempt to use a <a href="http://en.wikipedia.org/wiki/PubSubHubbub">PubSubHubbub</a> subscription if available.'),
165
      '#default_value' => $this->config['use_pubsubhubbub'],
166
    );
167
    $form['advanced'] = array(
168
      '#title' => t('Advanced settings'),
169
      '#type' => 'fieldset',
170
      '#collapsible' => TRUE,
171
      '#collapsed' => TRUE,
172
    );
173
    $form['advanced']['auto_scheme'] = array(
174
      '#type' => 'textfield',
175
      '#title' => t('Automatically add scheme'),
176
      '#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.'),
177
      '#default_value' => $this->config['auto_scheme'],
178
    );
179
    $form['advanced']['designated_hub'] = array(
180
      '#type' => 'textfield',
181
      '#title' => t('Designated hub'),
182
      '#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.'),
183
      '#default_value' => $this->config['designated_hub'],
184
      '#states' => array(
185
        'visible' => array(':input[name="use_pubsubhubbub"]' => array('checked' => TRUE)),
186
      ),
187
    );
188
    // Per importer override of global http request timeout setting.
189
    $form['advanced']['request_timeout'] = array(
190
      '#type' => 'textfield',
191
      '#title' => t('Request timeout'),
192
      '#description' => t('Timeout in seconds to wait for an HTTP get request to finish.</br>' .
193
                         '<b>Note:</b> this setting will override the global setting.</br>' .
194
                         'When left empty, the global value is used.'),
195
      '#default_value' => $this->config['request_timeout'],
196
      '#element_validate' => array('element_validate_integer_positive'),
197
      '#maxlength' => 3,
198
      '#size'=> 30,
199
    );
200
    $form['advanced']['accept_invalid_cert'] = array(
201
      '#type' => 'checkbox',
202
      '#title' => t('Accept invalid SSL certificates'),
203
      '#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.'),
204
      '#default_value' => $this->config['accept_invalid_cert'],
205
    );
206

    
207
    return $form;
208
  }
209

    
210
  /**
211
   * Expose source form.
212
   */
213
  public function sourceForm($source_config) {
214
    $form = array();
215
    $form['source'] = array(
216
      '#type' => 'textfield',
217
      '#title' => t('URL'),
218
      '#description' => t('Enter a feed URL.'),
219
      '#default_value' => isset($source_config['source']) ? $source_config['source'] : '',
220
      '#maxlength' => NULL,
221
      '#required' => TRUE,
222
    );
223
    return $form;
224
  }
225

    
226
  /**
227
   * Override parent::sourceFormValidate().
228
   */
229
  public function sourceFormValidate(&$values) {
230
    $values['source'] = trim($values['source']);
231

    
232
    // Keep a copy for error messages.
233
    $original_url = $values['source'];
234

    
235
    $parts = parse_url($values['source']);
236
    if (empty($parts['scheme']) && $this->config['auto_scheme']) {
237
      $values['source'] = $this->config['auto_scheme'] . '://' . $values['source'];
238
    }
239

    
240
    if (!feeds_valid_url($values['source'], TRUE)) {
241
      $form_key = 'feeds][' . get_class($this) . '][source';
242
      form_set_error($form_key, t('The URL %source is invalid.', array('%source' => $original_url)));
243
    }
244
    elseif ($this->config['auto_detect_feeds']) {
245
      feeds_include_library('http_request.inc', 'http_request');
246
      $url = http_request_get_common_syndication($values['source'], array(
247
        'accept_invalid_cert' => $this->config['accept_invalid_cert'],
248
      ));
249
      if ($url) {
250
        $values['source'] = $url;
251
      }
252
    }
253
  }
254

    
255
  /**
256
   * Override sourceSave() - subscribe to hub.
257
   */
258
  public function sourceSave(FeedsSource $source) {
259
    if ($this->config['use_pubsubhubbub']) {
260
      // If this is a feeds node we want to delay the subscription to
261
      // feeds_exit() to avoid transaction race conditions.
262
      if ($source->feed_nid) {
263
        $job = array('fetcher' => $this, 'source' => $source);
264
        feeds_set_subscription_job($job);
265
      }
266
      else {
267
        $this->subscribe($source);
268
      }
269
    }
270
  }
271

    
272
  /**
273
   * Override sourceDelete() - unsubscribe from hub.
274
   */
275
  public function sourceDelete(FeedsSource $source) {
276
    if ($this->config['use_pubsubhubbub']) {
277
      // If we're in a feed node, queue the unsubscribe,
278
      // else process immediately.
279
      if ($source->feed_nid) {
280
        $job = array(
281
          'type' => $source->id,
282
          'id' => $source->feed_nid,
283
          'period' => 0,
284
          'periodic' => FALSE,
285
        );
286
        JobScheduler::get('feeds_push_unsubscribe')->set($job);
287
      }
288
      else {
289
        $this->unsubscribe($source);
290
      }
291
    }
292
  }
293

    
294
  /**
295
   * Implement FeedsFetcher::subscribe() - subscribe to hub.
296
   */
297
  public function subscribe(FeedsSource $source) {
298
    $source_config = $source->getConfigFor($this);
299
    $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'] : '');
300
  }
301

    
302
  /**
303
   * Implement FeedsFetcher::unsubscribe() - unsubscribe from hub.
304
   */
305
  public function unsubscribe(FeedsSource $source) {
306
    $source_config = $source->getConfigFor($this);
307
    $this->subscriber($source->feed_nid)->unsubscribe($source_config['source'], url($this->path($source->feed_nid), array('absolute' => TRUE)));
308
  }
309

    
310
  /**
311
   * Implement FeedsFetcher::importPeriod().
312
   */
313
  public function importPeriod(FeedsSource $source) {
314
    if ($this->subscriber($source->feed_nid)->subscribed()) {
315
      return 259200; // Delay for three days if there is a successful subscription.
316
    }
317
  }
318

    
319
  /**
320
   * Convenience method for instantiating a subscriber object.
321
   */
322
  protected function subscriber($subscriber_id) {
323
    return PushSubscriber::instance($this->id, $subscriber_id, 'PuSHSubscription', PuSHEnvironment::instance());
324
  }
325
}
326

    
327
/**
328
 * Implement a PuSHSubscriptionInterface.
329
 */
330
class PuSHSubscription implements PuSHSubscriptionInterface {
331
  public $domain;
332
  public $subscriber_id;
333
  public $hub;
334
  public $topic;
335
  public $status;
336
  public $secret;
337
  public $post_fields;
338
  public $timestamp;
339

    
340
  /**
341
   * Load a subscription.
342
   */
343
  public static function load($domain, $subscriber_id) {
344
    if ($v = db_query("SELECT * FROM {feeds_push_subscriptions} WHERE domain = :domain AND subscriber_id = :sid", array(':domain' => $domain, ':sid' => $subscriber_id))->fetchAssoc()) {
345
      $v['post_fields'] = unserialize($v['post_fields']);
346
      return new PuSHSubscription($v['domain'], $v['subscriber_id'], $v['hub'], $v['topic'], $v['secret'], $v['status'], $v['post_fields'], $v['timestamp']);
347
    }
348
  }
349

    
350
  /**
351
   * Create a subscription.
352
   */
353
  public function __construct($domain, $subscriber_id, $hub, $topic, $secret, $status = '', $post_fields = '') {
354
    $this->domain = $domain;
355
    $this->subscriber_id = $subscriber_id;
356
    $this->hub = $hub;
357
    $this->topic = $topic;
358
    $this->status = $status;
359
    $this->secret = $secret;
360
    $this->post_fields = $post_fields;
361
  }
362

    
363
  /**
364
   * Save a subscription.
365
   */
366
  public function save() {
367
    $this->timestamp = time();
368
    $this->delete($this->domain, $this->subscriber_id);
369
    drupal_write_record('feeds_push_subscriptions', $this);
370
  }
371

    
372
  /**
373
   * Delete a subscription.
374
   */
375
  public function delete() {
376
    db_delete('feeds_push_subscriptions')
377
      ->condition('domain', $this->domain)
378
      ->condition('subscriber_id', $this->subscriber_id)
379
      ->execute();
380
  }
381
}
382

    
383
/**
384
 * Provide environmental functions to the PuSHSubscriber library.
385
 */
386
class PuSHEnvironment implements PuSHSubscriberEnvironmentInterface {
387
  /**
388
   * Singleton.
389
   */
390
  public static function instance() {
391
    static $env;
392
    if (empty($env)) {
393
      $env = new PuSHEnvironment();
394
    }
395
    return $env;
396
  }
397

    
398
  /**
399
   * Implements PuSHSubscriberEnvironmentInterface::msg().
400
   */
401
  public function msg($msg, $level = 'status') {
402
    drupal_set_message(check_plain($msg), $level);
403
  }
404

    
405
  /**
406
   * Implements PuSHSubscriberEnvironmentInterface::log().
407
   */
408
  public function log($msg, $level = 'status') {
409
    switch ($level) {
410
      case 'error':
411
        $severity = WATCHDOG_ERROR;
412
        break;
413
      case 'warning':
414
        $severity = WATCHDOG_WARNING;
415
        break;
416
      default:
417
        $severity = WATCHDOG_NOTICE;
418
        break;
419
    }
420
    feeds_dbg($msg);
421
    watchdog('FeedsHTTPFetcher', $msg, array(), $severity);
422
  }
423
}