Projet

Général

Profil

Paste
Télécharger (46 ko) Statistiques
| Branche: | Révision:

root / drupal7 / sites / all / modules / feeds / feeds.module @ ed9a13f1

1
<?php
2

    
3
/**
4
 * @file
5
 * Feeds - basic API functions and hook implementations.
6
 */
7

    
8
// Common request time, use as point of reference and to avoid calls to time().
9
define('FEEDS_REQUEST_TIME', time());
10
// Do not schedule a feed for refresh.
11
define('FEEDS_SCHEDULE_NEVER', -1);
12
// Never expire feed items.
13
define('FEEDS_EXPIRE_NEVER', -1);
14
// An object that is not persistent. Compare EXPORT_IN_DATABASE, EXPORT_IN_CODE.
15
define('FEEDS_EXPORT_NONE', 0x0);
16
// Status of batched operations.
17
define('FEEDS_BATCH_COMPLETE', 1.0);
18
define('FEEDS_BATCH_ACTIVE', 0.0);
19

    
20
/**
21
 * @defgroup hooks Hook and callback implementations
22
 * @{
23
 */
24

    
25
/**
26
 * Implements hook_hook_info().
27
 */
28
function feeds_hook_info() {
29
  $hooks = array(
30
    'feeds_plugins',
31
    'feeds_after_parse',
32
    'feeds_before_import',
33
    'feeds_before_update',
34
    'feeds_prevalidate',
35
    'feeds_presave',
36
    'feeds_after_save',
37
    'feeds_after_import',
38
    'feeds_after_clear',
39
    'feeds_processor_targets',
40
    'feeds_processor_targets_alter',
41
    'feeds_parser_sources_alter',
42
    'feeds_config_defaults',
43
    'feeds_fetcher_config_defaults',
44
    'feeds_parser_config_defaults',
45
    'feeds_processor_config_defaults',
46
  );
47

    
48
  return array_fill_keys($hooks, array('group' => 'feeds'));
49
}
50

    
51
/**
52
 * Implements hook_cron().
53
 */
54
function feeds_cron() {
55
  // Expire old log entries.
56
  db_delete('feeds_log')
57
    ->condition('request_time', REQUEST_TIME - 604800, '<')
58
    ->execute();
59

    
60
  // Find importers that need to be rescheduled.
61
  $importers = feeds_reschedule();
62
  if ($importers) {
63
    // @todo Maybe we should queue this somehow as well. This could be potentially
64
    // very long.
65
    $sources = db_query("SELECT feed_nid, id FROM {feeds_source} WHERE id IN (:ids)", array(':ids' => $importers));
66

    
67
    foreach ($sources as $source) {
68
      feeds_source($source->id, $source->feed_nid)->schedule();
69
    }
70

    
71
    feeds_reschedule(FALSE);
72
  }
73

    
74
  // Sync the files in the cache directory with entries in the cache every now
75
  // and then. By default: every six hours.
76
  $last_check = variable_get('feeds_sync_cache_feeds_http_last_check');
77
  $interval = variable_get('feeds_sync_cache_feeds_http_interval', 21600);
78
  if ($last_check < (REQUEST_TIME - $interval)) {
79
    // Check first if the task isn't already queued.
80
    $queue = DrupalQueue::get('feeds_sync_cache_feeds_http');
81
    if ($queue->numberOfItems() < 1) {
82
      // Queue sync task.
83
      FeedsHTTPCache::getInstance('cache_feeds_http')->startSync();
84
    }
85
    variable_set('feeds_sync_cache_feeds_http_last_check', REQUEST_TIME);
86
  }
87
}
88

    
89
/**
90
 * Implements hook_cron_job_scheduler_info().
91
 *
92
 * Compare queue names with key names in feeds_cron_queue_info().
93
 */
94
function feeds_cron_job_scheduler_info() {
95
  $info = array();
96
  $info['feeds_source_import'] = array(
97
    'queue name' => 'feeds_source_import',
98
  );
99
  // feeds_source_clear never gets called, since we now use the queue directly.
100
  // This is left in case any background jobs are still running after an
101
  // upgrade.
102
  $info['feeds_source_clear'] = array(
103
    'queue name' => 'feeds_source_clear',
104
  );
105
  $info['feeds_source_expire'] = array(
106
    'queue name' => 'feeds_source_expire',
107
  );
108
  $info['feeds_push_unsubscribe'] = array(
109
    'queue name' => 'feeds_push_unsubscribe',
110
  );
111
  return $info;
112
}
113

    
114
/**
115
 * Implements hook_cron_queue_info().
116
 */
117
function feeds_cron_queue_info() {
118
  $queues = array();
119
  $queues['feeds_source_import'] = array(
120
    'worker callback' => 'feeds_source_import',
121
    'time' => 60,
122
  );
123
  $queues['feeds_source_clear'] = array(
124
    'worker callback' => 'feeds_source_clear',
125
  );
126
  $queues['feeds_source_expire'] = array(
127
    'worker callback' => 'feeds_source_expire',
128
  );
129
  $queues['feeds_push_unsubscribe'] = array(
130
    'worker callback' => 'feeds_push_unsubscribe',
131
  );
132
  $queues['feeds_sync_cache_feeds_http'] = array(
133
    'worker callback' => 'feeds_sync_cache_feeds_http',
134
  );
135

    
136
  return $queues;
137
}
138

    
139
/**
140
 * Scheduler callback for importing from a source.
141
 *
142
 * @param array $job
143
 *   A job definition, which consists of at least the following elements:
144
 *   - type (string)
145
 *     The importer ID.
146
 *   - id (int)
147
 *     The Feed node ID if the importer is attached to a content type. Otherwise
148
 *     0.
149
 */
150
function feeds_source_import(array $job) {
151
  $source = _feeds_queue_worker_helper($job, 'import');
152
  if ($source->doesExist()) {
153
    $source->scheduleImport();
154
  }
155
}
156

    
157
/**
158
 * Scheduler callback for deleting all items from a source.
159
 *
160
 * @param array $job
161
 *   A job definition, which consists of at least the following elements:
162
 *   - type (string)
163
 *     The importer ID.
164
 *   - id (int)
165
 *     The Feed node ID if the importer is attached to a content type. Otherwise
166
 *     0.
167
 */
168
function feeds_source_clear(array $job) {
169
  $source = _feeds_queue_worker_helper($job, 'clear');
170
  if ($source->doesExist()) {
171
    $source->scheduleClear();
172
  }
173
}
174

    
175
/**
176
 * Scheduler callback for expiring content.
177
 *
178
 * @param array $job
179
 *   A job definition, which consists of at least the following elements:
180
 *   - type (string)
181
 *     The importer ID.
182
 *   - id (int)
183
 *     The Feed node ID if the importer is attached to a content type. Otherwise
184
 *     0.
185
 */
186
function feeds_source_expire(array $job) {
187
  $source = _feeds_queue_worker_helper($job, 'expire');
188
  if ($source->doesExist()) {
189
    $source->scheduleExpire();
190
  }
191
}
192

    
193
/**
194
 * Executes a method on a feed source.
195
 *
196
 * @param array $job
197
 *   A job definition, which consists of at least the following elements:
198
 *   - type (string)
199
 *     The importer ID.
200
 *   - id (int)
201
 *     The Feed node ID if the importer is attached to a content type. Otherwise
202
 *     0.
203
 * @param string $method
204
 *   The method to execute.
205
 */
206
function _feeds_queue_worker_helper(array $job, $method) {
207
  $source = feeds_source($job['type'], $job['id']);
208
  try {
209
    $source->existing()->$method();
210
  }
211
  catch (FeedsNotExistingException $e) {
212
    // Eventually remove the job from job scheduler.
213
    JobScheduler::get('feeds_source_import')->remove($job);
214
  }
215
  catch (Exception $e) {
216
    $source->log($method, $e->getMessage(), array(), WATCHDOG_ERROR);
217
  }
218

    
219
  return $source;
220
}
221

    
222
/**
223
 * Scheduler callback for keeping the cache dir and cache entries in sync.
224
 *
225
 * This makes sure that files that appear in the Feeds cache directory that are
226
 * no longer referenced in the cache_feeds_http bin, are cleaned up.
227
 * The entries saved in the cache_feeds_http bin and the actual cached files
228
 * saved on the file system can get out of sync when:
229
 * - Truncating the cache_feeds_http table manually.
230
 * - When using an alternative cache class for the cache_feeds_http bin
231
 *   (other than 'FeedsHTTPCache').
232
 */
233
function feeds_sync_cache_feeds_http(array $job) {
234
  FeedsHTTPCache::getInstance('cache_feeds_http')->sync($job['files']);
235
}
236

    
237
/**
238
 * Scheduler callback for unsubscribing from PuSH hubs.
239
 *
240
 * @param array $job
241
 *   A job definition, which consists of at least the following elements:
242
 *   - type (string)
243
 *     The importer ID.
244
 *   - id (int)
245
 *     The Feed node ID if the importer is attached to a content type. Otherwise
246
 *     0.
247
 */
248
function feeds_push_unsubscribe($job) {
249
  $source = feeds_source($job['type'], $job['id']);
250
  /** @var FeedsFetcher $fetcher */
251
  $fetcher = feeds_plugin('FeedsHTTPFetcher', $source->importer->id);
252
  $fetcher->unsubscribe($source);
253
}
254

    
255
/**
256
 * Batch API worker callback. Used by FeedsSource::startBatchAPIJob().
257
 *
258
 * @todo Harmonize Job Scheduler API callbacks with Batch API callbacks?
259
 *
260
 * @param string $method
261
 *   Method to execute on importer; one of 'import' or 'clear'.
262
 * @param string $importer_id
263
 *   Identifier of a FeedsImporter object.
264
 * @param int $feed_nid
265
 *   If importer is attached to content type, feed node id identifying the
266
 *   source to be imported.
267
 * @param array $context
268
 *   Batch context.
269
 *
270
 * @see FeedsSource::startBatchAPIJob()
271
 */
272
function feeds_batch($method, $importer_id, $feed_nid = 0, &$context) {
273
  $context['finished'] = FEEDS_BATCH_COMPLETE;
274
  try {
275
    $context['finished'] = feeds_source($importer_id, $feed_nid)->$method();
276
  }
277
  catch (Exception $e) {
278
    drupal_set_message($e->getMessage(), 'error');
279
  }
280
}
281

    
282
/**
283
 * Reschedule one or all importers.
284
 *
285
 * @param string|bool|null $importer_id
286
 *   If TRUE, all importers will be rescheduled, if FALSE, no importers will
287
 *   be rescheduled, if an importer id, only importer of that id will be
288
 *   rescheduled.
289
 *
290
 * @return string[]
291
 *   A list of importer ids that need rescheduling.
292
 */
293
function feeds_reschedule($importer_id = NULL) {
294
  $reschedule = variable_get('feeds_reschedule', FALSE);
295

    
296
  if ($importer_id === TRUE || $importer_id === FALSE) {
297
    $reschedule = $importer_id;
298
  }
299
  elseif (is_string($importer_id) && $reschedule !== TRUE) {
300
    $reschedule = array_filter((array) $reschedule);
301
    $reschedule[$importer_id] = $importer_id;
302
  }
303

    
304
  if (isset($importer_id)) {
305
    variable_set('feeds_reschedule', $reschedule);
306
  }
307

    
308
  if ($reschedule === TRUE) {
309
    return feeds_enabled_importers();
310
  }
311
  elseif ($reschedule === FALSE) {
312
    return array();
313
  }
314

    
315
  return $reschedule;
316
}
317

    
318
/**
319
 * Implements hook_permission().
320
 */
321
function feeds_permission() {
322
  $perms = array(
323
    'administer feeds' => array(
324
      'title' => t('Administer Feeds'),
325
      'description' => t('Create, update, delete importers, execute import and delete tasks on any importer.'),
326
    ),
327
  );
328
  foreach (feeds_importer_load_all() as $importer) {
329
    $perms["import $importer->id feeds"] = array(
330
      'title' => t('Import @name feeds', array('@name' => $importer->config['name'])),
331
    );
332
    $perms["clear $importer->id feeds"] = array(
333
      'title' => t('Delete items from @name feeds', array('@name' => $importer->config['name'])),
334
    );
335
    $perms["unlock $importer->id feeds"] = array(
336
      'title' => t('Unlock imports from @name feeds', array('@name' => $importer->config['name'])),
337
      'description' => t('If a feed importation breaks for some reason, users with this permission can unlock them.'),
338
    );
339
  }
340
  return $perms;
341
}
342

    
343
/**
344
 * Implements hook_forms().
345
 *
346
 * Declare form callbacks for all known classes derived from FeedsConfigurable.
347
 */
348
function feeds_forms($form_id, $args) {
349
  // Check if the requested form is a Feeds form.
350
  if (!stripos($form_id, '_feeds_form')) {
351
    return;
352
  }
353

    
354
  $forms = array();
355
  $forms['FeedsImporter_feeds_form']['callback'] = 'feeds_form';
356
  $plugins = FeedsPlugin::all();
357
  foreach ($plugins as $plugin) {
358
    $forms[$plugin['handler']['class'] . '_feeds_form']['callback'] = 'feeds_form';
359
  }
360
  return $forms;
361
}
362

    
363
/**
364
 * Implements hook_menu().
365
 */
366
function feeds_menu() {
367
  $items = array();
368
  $items['import'] = array(
369
    'title' => 'Import',
370
    'page callback' => 'feeds_page',
371
    'access callback' => 'feeds_page_access',
372
    'file' => 'feeds.pages.inc',
373
  );
374
  $items['import/%feeds_importer'] = array(
375
    'title callback' => 'feeds_importer_title',
376
    'title arguments' => array(1),
377
    'page callback' => 'drupal_get_form',
378
    'page arguments' => array('feeds_import_form', 1),
379
    'access callback' => 'feeds_access',
380
    'access arguments' => array('import', 1),
381
    'file' => 'feeds.pages.inc',
382
  );
383
  $items['import/%feeds_importer/import'] = array(
384
    'title' => 'Import',
385
    'type' => MENU_DEFAULT_LOCAL_TASK,
386
    'weight' => -10,
387
  );
388
  $items['import/%feeds_importer/delete-items'] = array(
389
    'title' => 'Delete items',
390
    'page callback' => 'drupal_get_form',
391
    'page arguments' => array('feeds_delete_tab_form', 1),
392
    'access callback' => 'feeds_access',
393
    'access arguments' => array('clear', 1),
394
    'file' => 'feeds.pages.inc',
395
    'type' => MENU_LOCAL_TASK,
396
  );
397
  $items['import/%feeds_importer/unlock'] = array(
398
    'title' => 'Unlock',
399
    'page callback' => 'drupal_get_form',
400
    'page arguments' => array('feeds_unlock_tab_form', 1),
401
    'access callback' => 'feeds_access',
402
    'access arguments' => array('unlock', 1),
403
    'file' => 'feeds.pages.inc',
404
    'type' => MENU_LOCAL_TASK,
405
  );
406
  $items['import/%feeds_importer/template'] = array(
407
    'page callback' => 'feeds_importer_template',
408
    'page arguments' => array(1),
409
    'access callback' => 'feeds_access',
410
    'access arguments' => array('import', 1),
411
    'file' => 'feeds.pages.inc',
412
    'type' => MENU_CALLBACK,
413
  );
414
  $items['node/%node/import'] = array(
415
    'title' => 'Import',
416
    'page callback' => 'drupal_get_form',
417
    'page arguments' => array('feeds_import_tab_form', 1),
418
    'access callback' => 'feeds_access',
419
    'access arguments' => array('import', 1),
420
    'file' => 'feeds.pages.inc',
421
    'type' => MENU_LOCAL_TASK,
422
    'weight' => 10,
423
  );
424
  $items['node/%node/delete-items'] = array(
425
    'title' => 'Delete items',
426
    'page callback' => 'drupal_get_form',
427
    'page arguments' => array('feeds_delete_tab_form', NULL, 1),
428
    'access callback' => 'feeds_access',
429
    'access arguments' => array('clear', 1),
430
    'file' => 'feeds.pages.inc',
431
    'type' => MENU_LOCAL_TASK,
432
    'weight' => 11,
433
  );
434
  $items['node/%node/unlock'] = array(
435
    'title' => 'Unlock',
436
    'page callback' => 'drupal_get_form',
437
    'page arguments' => array('feeds_unlock_tab_form', NULL, 1),
438
    'access callback' => 'feeds_access',
439
    'access arguments' => array('unlock', 1),
440
    'file' => 'feeds.pages.inc',
441
    'type' => MENU_LOCAL_TASK,
442
    'weight' => 11,
443
  );
444
  // @todo Eliminate this step and thus eliminate clearing menu cache when
445
  // manipulating importers.
446
  foreach (feeds_importer_load_all() as $importer) {
447
    $items += $importer->fetcher->menuItem();
448
  }
449
  return $items;
450
}
451

    
452
/**
453
 * Implements hook_admin_paths().
454
 */
455
function feeds_admin_paths() {
456
  $paths = array(
457
    'import' => TRUE,
458
    'import/*' => TRUE,
459
    'node/*/import' => TRUE,
460
    'node/*/delete-items' => TRUE,
461
    'node/*/log' => TRUE,
462
  );
463
  return $paths;
464
}
465

    
466
/**
467
 * Menu loader callback.
468
 *
469
 * @param string $id
470
 *   The ID of the importer to load.
471
 *
472
 * @return FeedsImporter|false
473
 *   A FeedsImporter instance if found. False otherwise.
474
 */
475
function feeds_importer_load($id) {
476
  try {
477
    $importer = feeds_importer($id);
478
    if ($importer->doesExist()) {
479
      return $importer;
480
    }
481
  }
482
  catch (InvalidArgumentException $e) {
483
  }
484

    
485
  return FALSE;
486
}
487

    
488
/**
489
 * Title callback.
490
 *
491
 * @param FeedsImporter $importer
492
 *   The importer to return the page title for.
493
 *
494
 * @return string
495
 *   A page title.
496
 */
497
function feeds_importer_title(FeedsImporter $importer) {
498
  return $importer->config['name'];
499
}
500

    
501
/**
502
 * Implements hook_theme().
503
 */
504
function feeds_theme() {
505
  return array(
506
    'feeds_upload' => array(
507
      'file' => 'feeds.pages.inc',
508
      'render element' => 'element',
509
    ),
510
    'feeds_source_status' => array(
511
      'file' => 'feeds.pages.inc',
512
      'variables' => array(
513
        'progress_importing' => NULL,
514
        'progress_clearing' => NULL,
515
        'imported' => NULL,
516
        'count' => NULL,
517
      ),
518
    ),
519
  );
520
}
521

    
522
/**
523
 * Menu access callback.
524
 *
525
 * @param string $action
526
 *   The action to be performed. Possible values are:
527
 *   - import;
528
 *   - clear;
529
 *   - unlock.
530
 * @param object|string $param
531
 *   Node object or FeedsImporter id.
532
 *
533
 * @return bool
534
 *   True if access is granted. False otherwise.
535
 */
536
function feeds_access($action, $param) {
537
  if (!in_array($action, array('import', 'clear', 'unlock'))) {
538
    // If $action is not one of the supported actions, we return access denied.
539
    return FALSE;
540
  }
541

    
542
  $importer_id = FALSE;
543
  if (is_string($param)) {
544
    $importer_id = $param;
545
  }
546
  elseif ($param instanceof FeedsImporter) {
547
    $importer_id = $param->id;
548
  }
549
  elseif ($param->type) {
550
    $importer_id = feeds_get_importer_id($param->type);
551
  }
552

    
553
  // Check for permissions if feed id is present, otherwise return FALSE.
554
  if ($importer_id) {
555
    if (user_access('administer feeds') || user_access("{$action} {$importer_id} feeds")) {
556
      return TRUE;
557
    }
558
  }
559
  return FALSE;
560
}
561

    
562
/**
563
 * Access callback to determine if the user can import Feeds importers.
564
 *
565
 * Feeds imports require an additional access check because they are PHP
566
 * code and PHP is more locked down than administer feeds.
567
 *
568
 * @return bool
569
 *   True if access is granted. False otherwise.
570
 */
571
function feeds_importer_import_access() {
572
  return user_access('administer feeds') && user_access('use PHP for settings');
573
}
574

    
575
/**
576
 * Menu access callback.
577
 *
578
 * @return bool
579
 *   True if access is granted. False otherwise.
580
 */
581
function feeds_page_access() {
582
  if (user_access('administer feeds')) {
583
    return TRUE;
584
  }
585
  foreach (feeds_enabled_importers() as $id) {
586
    if (user_access("import $id feeds")) {
587
      return TRUE;
588
    }
589
  }
590
  return FALSE;
591
}
592

    
593
/**
594
 * Implements hook_exit().
595
 */
596
function feeds_exit() {
597
  // Process any pending PuSH subscriptions.
598
  $jobs = feeds_get_subscription_jobs();
599
  foreach ($jobs as $job) {
600
    if (!isset($job['fetcher']) || !isset($job['source'])) {
601
      continue;
602
    }
603
    $job['fetcher']->subscribe($job['source']);
604
  }
605

    
606
  if (drupal_static('feeds_log_error', FALSE)) {
607
    watchdog('feeds', 'Feeds reported errors, visit the Feeds log for details.', array(), WATCHDOG_ERROR, l(t('view'), 'admin/reports/feeds'));
608
  }
609
}
610

    
611
/**
612
 * Implements hook_views_api().
613
 */
614
function feeds_views_api() {
615
  return array(
616
    'api' => 3,
617
    'path' => drupal_get_path('module', 'feeds') . '/views',
618
  );
619
}
620

    
621
/**
622
 * Implements hook_ctools_plugin_api().
623
 */
624
function feeds_ctools_plugin_api($owner, $api) {
625
  if ($owner == 'feeds' && $api == 'plugins') {
626
    return array('version' => 1);
627
  }
628
}
629

    
630
/**
631
 * Implements hook_ctools_plugin_type().
632
 */
633
function feeds_ctools_plugin_type() {
634
  return array(
635
    'plugins' => array(
636
      'cache' => TRUE,
637
      'use hooks' => TRUE,
638
      'classes' => array('handler'),
639
    ),
640
  );
641
}
642

    
643
/**
644
 * Implements hook_feeds_plugins().
645
 */
646
function feeds_feeds_plugins() {
647
  module_load_include('inc', 'feeds', 'feeds.plugins');
648
  return _feeds_feeds_plugins();
649
}
650

    
651
/**
652
 * Gets the feed_nid for a single entity.
653
 *
654
 * @param int $entity_id
655
 *   The entity id.
656
 * @param string $entity_type
657
 *   The type of entity.
658
 *
659
 * @return int|bool
660
 *   The feed_nid of the entity, or FALSE if the entity doesn't belong to a
661
 *   feed.
662
 */
663
function feeds_get_feed_nid($entity_id, $entity_type) {
664
  return db_query("SELECT feed_nid FROM {feeds_item} WHERE entity_type = :type AND entity_id = :id", array(':type' => $entity_type, ':id' => $entity_id))->fetchField();
665
}
666

    
667
/**
668
 * Implements hook_entity_insert().
669
 */
670
function feeds_entity_insert($entity, $type) {
671
  list($id) = entity_extract_ids($type, $entity);
672
  feeds_item_info_insert($entity, $id);
673
}
674

    
675
/**
676
 * Implements hook_entity_update().
677
 */
678
function feeds_entity_update($entity, $type) {
679
  list($id) = entity_extract_ids($type, $entity);
680
  feeds_item_info_save($entity, $id);
681
}
682

    
683
/**
684
 * Implements hook_entity_delete().
685
 */
686
function feeds_entity_delete($entity, $type) {
687
  list($id) = entity_extract_ids($type, $entity);
688

    
689
  // Delete any imported items produced by the source.
690
  db_delete('feeds_item')
691
    ->condition('entity_type', $type)
692
    ->condition('entity_id', $id)
693
    ->execute();
694
}
695

    
696
/**
697
 * Implements hook_node_validate().
698
 */
699
function feeds_node_validate($node, $form, &$form_state) {
700
  if (!$importer_id = feeds_get_importer_id($node->type)) {
701
    return;
702
  }
703
  // Keep a copy of the title for subsequent node creation stages.
704
  // @todo: revisit whether $node still looses all of its properties
705
  // between validate and insert stage.
706
  $last_title = &drupal_static('feeds_node_last_title');
707
  $last_feeds = &drupal_static('feeds_node_last_feeds');
708

    
709
  // On validation stage we are working with a FeedsSource object that is
710
  // not tied to a nid - when creating a new node there is no
711
  // $node->nid at this stage.
712
  $source = feeds_source($importer_id);
713

    
714
  // Node module magically moved $form['feeds'] to $node->feeds :P.
715
  // configFormValidate may modify $last_feed, smuggle it to update/insert stage
716
  // through a static variable.
717
  $last_feeds = $node->feeds;
718
  $source->configFormValidate($last_feeds);
719

    
720
  // Check if title form element is hidden.
721
  $title_hidden = (isset($form['title']['#access']) && !$form['title']['#access']);
722

    
723
  // If the node title is empty and the title form element wasn't hidden, try to
724
  // retrieve the title from the feed.
725
  if (isset($node->title) && trim($node->title) == '' && !$title_hidden) {
726
    try {
727
      $source->addConfig($last_feeds);
728
      if (!$last_title = $source->preview()->title) {
729
        throw new Exception();
730
      }
731
    }
732
    catch (Exception $e) {
733
      drupal_set_message($e->getMessage(), 'error');
734
      form_set_error('title', t('Could not retrieve title from feed.'));
735
    }
736
  }
737
}
738

    
739
/**
740
 * Implements hook_node_presave().
741
 */
742
function feeds_node_presave($node) {
743
  // Populate $node->title and $node->feed from result of validation phase.
744
  $last_title = &drupal_static('feeds_node_last_title');
745
  $last_feeds = &drupal_static('feeds_node_last_feeds');
746
  if (empty($node->title) && !empty($last_title)) {
747
    $node->title = $last_title;
748
  }
749
  if (!empty($last_feeds)) {
750
    $node->feeds = $last_feeds;
751
  }
752
  $last_title = NULL;
753
  $last_feeds = NULL;
754

    
755
  // Update "changed" value if there was mapped to that.
756
  if (isset($node->feeds_item->node_changed)) {
757
    $node->changed = $node->feeds_item->node_changed;
758
  }
759
}
760

    
761
/**
762
 * Implements hook_node_insert().
763
 */
764
function feeds_node_insert($node) {
765
  // Source attached to node.
766
  if (isset($node->feeds) && $importer_id = feeds_get_importer_id($node->type)) {
767
    $source = feeds_source($importer_id, $node->nid);
768
    $source->addConfig($node->feeds);
769
    $source->save();
770

    
771
    // Start import if requested.
772
    if (feeds_importer($importer_id)->config['import_on_create'] && !isset($node->feeds['suppress_import'])) {
773
      $source->startImport();
774
    }
775
    // Schedule the source.
776
    $source->schedule();
777
  }
778
}
779

    
780
/**
781
 * Implements hook_node_update().
782
 */
783
function feeds_node_update($node) {
784
  // Source attached to node.
785
  if (isset($node->feeds) && $importer_id = feeds_get_importer_id($node->type)) {
786
    $source = feeds_source($importer_id, $node->nid);
787
    $source->addConfig($node->feeds);
788
    $source->save();
789
    $source->ensureSchedule();
790
  }
791
}
792

    
793
/**
794
 * Implements hook_node_delete().
795
 */
796
function feeds_node_delete($node) {
797
  // Source attached to node.
798
  // Make sure we don't leave any orphans behind: Do not use
799
  // feeds_get_importer_id() to determine importer id as the importer may have
800
  // been deleted.
801
  if ($importer_id = db_query("SELECT id FROM {feeds_source} WHERE feed_nid = :nid", array(':nid' => $node->nid))->fetchField()) {
802
    feeds_source($importer_id, $node->nid)->delete();
803
  }
804
}
805

    
806
/**
807
 * Implements hook_form_BASE_FORM_ID_alter().
808
 */
809
function feeds_form_node_form_alter(&$form, $form_state) {
810
  if ($importer_id = feeds_get_importer_id($form['#node']->type)) {
811
    // Enable uploads.
812
    $form['#attributes']['enctype'] = 'multipart/form-data';
813

    
814
    // Build form.
815
    $source = feeds_source($importer_id, empty($form['#node']->nid) ? NULL : $form['#node']->nid);
816
    $form['feeds'] = array(
817
      '#type' => 'fieldset',
818
      '#title' => t('Feed'),
819
      '#tree' => TRUE,
820
      '#weight' => 0,
821
    );
822
    $form['feeds'] += $source->configForm($form_state);
823
    $form['#feed_id'] = $importer_id;
824

    
825
    // If the parser has support for delivering a source title, set node title
826
    // to not required and try to retrieve it from the source if the node title
827
    // is left empty by the user.
828
    // @see feeds_node_validate()
829
    if (isset($form['title']) && $source->importer()->parser->providesSourceTitle()) {
830
      $form['title']['#required'] = FALSE;
831
    }
832
  }
833
}
834

    
835
/**
836
 * Implements hook_field_extra_fields().
837
 */
838
function feeds_field_extra_fields() {
839
  $extras = array();
840
  foreach (node_type_get_names() as $type => $name) {
841
    if (feeds_get_importer_id($type)) {
842
      $extras['node'][$type]['form']['feeds'] = array(
843
        'label' => t('Feed'),
844
        'description' => t('Feeds module form elements'),
845
        'weight' => 0,
846
      );
847
    }
848
  }
849
  return $extras;
850
}
851

    
852
/**
853
 * Implements hook_features_pipe_COMPONENT_alter() for 'feeds_importer'.
854
 *
855
 * Automatically adds dependencies when a Feed importer is selected in Features.
856
 */
857
function feeds_features_pipe_feeds_importer_alter(&$pipe, $data, &$export) {
858
  foreach ($data as $importer_id) {
859
    if ($importer = feeds_importer_load($importer_id)) {
860
      $export['dependencies'] = array_merge($export['dependencies'], $importer->dependencies());
861
    }
862
  }
863
}
864

    
865
/**
866
 * Implements hook_system_info_alter().
867
 *
868
 * Goes through a list of all modules that provide Feeds plugins and makes them
869
 * required if there are any importers using those plugins.
870
 */
871
function feeds_system_info_alter(array &$info, $file, $type) {
872
  if ($type !== 'module' || !module_exists($file->name) || !module_hook($file->name, 'feeds_plugins')) {
873
    return;
874
  }
875

    
876
  // Don't make Feeds require itself, otherwise you can't disable Feeds until
877
  // all importers are deleted.
878
  if ($file->name === 'feeds' || !function_exists('ctools_include')) {
879
    return;
880
  }
881

    
882
  // Get the plugins that belong to the current module.
883
  ctools_include('plugins');
884
  $module_plugins = array();
885
  foreach (ctools_get_plugins('feeds', 'plugins') as $plugin_id => $plugin) {
886
    if ($file->name === $plugin['module']) {
887
      $module_plugins[$plugin_id] = TRUE;
888
    }
889
  }
890

    
891
  // Check if any importers are using any plugins from the current module.
892
  foreach (feeds_importer_load_all(TRUE) as $importer) {
893

    
894
    // Skip importers that are defined in code and are provided by the current
895
    // module. This ensures that modules that define both an importer and a
896
    // plugin can still be disabled.
897
    if ($importer->export_type == EXPORT_IN_CODE) {
898
      $configs = ctools_export_load_object('feeds_importer', 'names', array($importer->id));
899
      if (isset($configs[$importer->id]) && $configs[$importer->id]->export_module === $file->name) {
900
        continue;
901
      }
902
    }
903

    
904
    $configuration = $importer->getConfig();
905

    
906
    foreach (array('fetcher', 'parser', 'processor') as $plugin_type) {
907
      $plugin_key = $configuration[$plugin_type]['plugin_key'];
908
      if (isset($module_plugins[$plugin_key])) {
909
        $info['required'] = TRUE;
910
        break 2;
911
      }
912
    }
913
  }
914

    
915
  if (empty($info['required'])) {
916
    return;
917
  }
918

    
919
  if (module_exists('feeds_ui') && user_access('administer feeds')) {
920
    $info['explanation'] = t('Feeds is currently using this module for one or more <a href="@link">importers</a>', array('@link' => url('admin/structure/feeds')));
921
  }
922
  else {
923
    $info['explanation'] = t('Feeds is currently using this module for one or more importers');
924
  }
925
}
926

    
927
/**
928
 * Implements hook_module_implements_alter().
929
 */
930
function feeds_module_implements_alter(array &$implementations, $hook) {
931
  if ($hook === 'feeds_processor_targets_alter') {
932
    // We need two implementations of this hook, so we add one that gets
933
    // called first, and move the normal one to last.
934
    $implementations = array('_feeds' => FALSE) + $implementations;
935

    
936
    // Move normal implementation to last.
937
    $group = $implementations['feeds'];
938
    unset($implementations['feeds']);
939
    $implementations['feeds'] = $group;
940
  }
941
}
942

    
943
/**
944
 * Implements hook_feeds_processor_targets_alter().
945
 *
946
 * @see feeds_feeds_processor_targets()
947
 * @see feeds_feeds_processor_targets_alter()
948
 */
949
function _feeds_feeds_processor_targets_alter(array &$targets, $entity_type, $bundle) {
950
  // If hook_feeds_processor_targets() hasn't been called, for instance, by
951
  // older processors, invoke it ourself.
952
  if (!drupal_static('feeds_feeds_processor_targets', FALSE)) {
953
    $targets += module_invoke_all('feeds_processor_targets', $entity_type, $bundle);
954
  }
955
}
956

    
957
/**
958
 * Implements hook_flush_caches().
959
 */
960
function feeds_flush_caches() {
961
  // The update to add the table needs to have run. Taken from
962
  // https://www.drupal.org/node/2511858
963
  include_once DRUPAL_ROOT . '/includes/install.inc';
964

    
965
  if (drupal_get_installed_schema_version('feeds') >= 7212) {
966
    return array('cache_feeds_http');
967
  }
968
  return array();
969
}
970

    
971
/**
972
 * Implements hook_admin_menu_output_build().
973
 *
974
 * Shows available importers in the "Content" section of the admin menu.
975
 * Requires the "admin_menu" module to be enabled.
976
 */
977
function feeds_admin_menu_output_build(array &$content) {
978
  // Add new top-level item to the menu.
979
  if (!isset($content['menu'])) {
980
    return;
981
  }
982
  $access = FALSE;
983
  foreach (feeds_enabled_importers() as $importer_id) {
984
    if (user_access('administer feeds') || user_access("import $importer_id feeds")) {
985
      $access = TRUE;
986
      break;
987
    }
988
  }
989

    
990
  if (!$access) {
991
    return;
992
  }
993

    
994
  $content['menu']['admin/content']['admin/content/feeds_import'] = array(
995
    '#title' => t('Import'),
996
    '#href' => 'import',
997
  );
998

    
999
  foreach (feeds_importer_load_all() as $importer) {
1000
    $content['menu']['admin/content']['admin/content/feeds_import'][$importer->id] = array(
1001
      '#title' => check_plain($importer->config['name']),
1002
      '#href' => !empty($importer->config['content_type']) ? 'node/add/' . str_replace('_', '-', $importer->config['content_type']) : 'import/' . check_plain($importer->id),
1003
      '#access' => user_access('administer feeds') || user_access("import $importer->id feeds"),
1004
    );
1005
  }
1006
}
1007

    
1008
/**
1009
 * Implements hook_menu_local_tasks_alter().
1010
 *
1011
 * Adds "Import" link as local action on content overview page.
1012
 */
1013
function feeds_menu_local_tasks_alter(&$data, $router_item, $root_path) {
1014
  if ($root_path == 'admin/content') {
1015
    $data['actions']['output'][] = array(
1016
      '#theme' => 'menu_local_task',
1017
      '#link' => array(
1018
        'title' => t('Import'),
1019
        'href' => 'import',
1020
        'localized_options' => array(
1021
          'attributes' => array(
1022
            'title' => t('Import'),
1023
          ),
1024
        ),
1025
      ),
1026
      '#access' => feeds_page_access(),
1027
      // Add weight so it appears after the local action "Add content".
1028
      '#weight' => 1,
1029
    );
1030
  }
1031
}
1032

    
1033
/**
1034
 * @}
1035
 */
1036

    
1037
/**
1038
 * @defgroup utility Utility functions
1039
 * @{
1040
 */
1041

    
1042
/**
1043
 * Loads all importers.
1044
 *
1045
 * @param bool $load_disabled
1046
 *   Pass TRUE to load all importers, enabled or disabled, pass FALSE to only
1047
 *   retrieve enabled importers.
1048
 *
1049
 * @return array
1050
 *   An array of all feed configurations available.
1051
 */
1052
function feeds_importer_load_all($load_disabled = FALSE) {
1053
  $feeds = array();
1054
  // This function can get called very early in install process through
1055
  // menu_router_rebuild(). Do not try to include CTools if not available.
1056
  if (function_exists('ctools_include')) {
1057
    ctools_include('export');
1058
    $configs = ctools_export_load_object('feeds_importer', 'all');
1059
    foreach ($configs as $config) {
1060
      if (!empty($config->id) && ($load_disabled || empty($config->disabled))) {
1061
        $feeds[$config->id] = feeds_importer($config->id);
1062
      }
1063
    }
1064
    uasort($feeds, 'feeds_importer_name_sort');
1065
  }
1066
  return $feeds;
1067
}
1068

    
1069
/**
1070
 * Sorts importers by name.
1071
 *
1072
 * Callback for uasort().
1073
 *
1074
 * @param FeedsImporter $a
1075
 *   The first FeedsImporter for comparison.
1076
 * @param FeedsImporter $b
1077
 *   The second FeedsImporter for comparison.
1078
 *
1079
 * @return int
1080
 *   The comparison result for uasort().
1081
 */
1082
function feeds_importer_name_sort(FeedsImporter $a, FeedsImporter $b) {
1083
  return strcasecmp($a->config['name'], $b->config['name']);
1084
}
1085

    
1086
/**
1087
 * Gets an array of enabled importer ids.
1088
 *
1089
 * @return string[]
1090
 *   An array where the values contain ids of enabled importers.
1091
 */
1092
function feeds_enabled_importers() {
1093
  return array_keys(_feeds_importer_digest());
1094
}
1095

    
1096
/**
1097
 * Gets an enabled importer configuration by content type.
1098
 *
1099
 * @param string $content_type
1100
 *   A node type string.
1101
 *
1102
 * @return string|false
1103
 *   A FeedsImporter id if there is an importer for the given content type,
1104
 *   FALSE otherwise.
1105
 */
1106
function feeds_get_importer_id($content_type) {
1107
  $importers = array_flip(_feeds_importer_digest());
1108
  return isset($importers[$content_type]) ? $importers[$content_type] : FALSE;
1109
}
1110

    
1111
/**
1112
 * Helper function for feeds_get_importer_id() and feeds_enabled_importers().
1113
 */
1114
function _feeds_importer_digest() {
1115
  $importers = &drupal_static(__FUNCTION__);
1116
  if ($importers === NULL) {
1117
    if ($cache = cache_get(__FUNCTION__)) {
1118
      $importers = $cache->data;
1119
    }
1120
    else {
1121
      $importers = array();
1122
      foreach (feeds_importer_load_all() as $importer) {
1123
        $importers[$importer->id] = isset($importer->config['content_type']) ? $importer->config['content_type'] : '';
1124
      }
1125
      cache_set(__FUNCTION__, $importers);
1126
    }
1127
  }
1128
  return $importers;
1129
}
1130

    
1131
/**
1132
 * Resets importer caches. Call when enabling/disabling importers.
1133
 */
1134
function feeds_cache_clear($rebuild_menu = TRUE) {
1135
  cache_clear_all('_feeds_importer_digest', 'cache');
1136
  drupal_static_reset('_feeds_importer_digest');
1137
  cache_clear_all('plugins:feeds:plugins', 'cache');
1138
  ctools_include('export');
1139
  ctools_export_load_object_reset('feeds_importer');
1140
  drupal_static_reset('_node_types_build');
1141
  if ($rebuild_menu) {
1142
    menu_rebuild();
1143
  }
1144
}
1145

    
1146
/**
1147
 * Exports a FeedsImporter configuration to code.
1148
 */
1149
function feeds_export($importer_id, $indent = '') {
1150
  ctools_include('export');
1151
  $result = ctools_export_load_object('feeds_importer', 'names', array('id' => $importer_id));
1152
  if (isset($result[$importer_id])) {
1153
    return ctools_export_object('feeds_importer', $result[$importer_id], $indent);
1154
  }
1155
}
1156

    
1157
/**
1158
 * Logs to a file like /tmp/feeds_my_domain_org.log in temporary directory.
1159
 */
1160
function feeds_dbg($msg) {
1161
  if (variable_get('feeds_debug', FALSE)) {
1162
    if (!is_string($msg)) {
1163
      $msg = var_export($msg, TRUE);
1164
    }
1165
    $filename = trim(str_replace('/', '_', $_SERVER['HTTP_HOST'] . base_path()), '_');
1166
    $handle = fopen("temporary://feeds_$filename.log", 'a');
1167
    fwrite($handle, gmdate('c') . "\t$msg\n");
1168
    fclose($handle);
1169
  }
1170
}
1171

    
1172
/**
1173
 * Writes to feeds log.
1174
 */
1175
function feeds_log($importer_id, $feed_nid, $type, $message, $variables = array(), $severity = WATCHDOG_NOTICE) {
1176
  if ($severity < WATCHDOG_NOTICE) {
1177
    $error = &drupal_static('feeds_log_error', FALSE);
1178
    $error = TRUE;
1179
  }
1180
  db_insert('feeds_log')
1181
    ->fields(array(
1182
      'id' => $importer_id,
1183
      'feed_nid' => $feed_nid,
1184
      'log_time' => time(),
1185
      'request_time' => REQUEST_TIME,
1186
      'type' => $type,
1187
      'message' => $message,
1188
      'variables' => serialize($variables),
1189
      'severity' => $severity,
1190
    ))
1191
    ->execute();
1192
}
1193

    
1194
/**
1195
 * Loads an item info object.
1196
 *
1197
 * Example usage:
1198
 *
1199
 * $info = feeds_item_info_load('node', $node->nid);
1200
 */
1201
function feeds_item_info_load($entity_type, $entity_id) {
1202
  return db_select('feeds_item')
1203
    ->fields('feeds_item')
1204
    ->condition('entity_type', $entity_type)
1205
    ->condition('entity_id', $entity_id)
1206
    ->execute()
1207
    ->fetchObject();
1208
}
1209

    
1210
/**
1211
 * Inserts an item info object into the feeds_item table.
1212
 */
1213
function feeds_item_info_insert($entity, $entity_id) {
1214
  if (isset($entity->feeds_item)) {
1215
    $entity->feeds_item->entity_id = $entity_id;
1216
    drupal_write_record('feeds_item', $entity->feeds_item);
1217
  }
1218
}
1219

    
1220
/**
1221
 * Inserts or updates an item info object in the feeds_item table.
1222
 */
1223
function feeds_item_info_save($entity, $entity_id) {
1224
  if (isset($entity->feeds_item)) {
1225
    $entity->feeds_item->entity_id = $entity_id;
1226
    if (feeds_item_info_load($entity->feeds_item->entity_type, $entity_id)) {
1227
      drupal_write_record('feeds_item', $entity->feeds_item, array('entity_type', 'entity_id'));
1228
    }
1229
    else {
1230
      feeds_item_info_insert($entity, $entity_id);
1231
    }
1232
  }
1233
}
1234

    
1235
/**
1236
 * @}
1237
 */
1238

    
1239
/**
1240
 * @defgroup instantiators Instantiators
1241
 * @{
1242
 */
1243

    
1244
/**
1245
 * Gets an importer instance.
1246
 *
1247
 * @param string $id
1248
 *   The unique id of the importer object.
1249
 *
1250
 * @return FeedsImporter
1251
 *   A FeedsImporter object or an object of a class defined by the Drupal
1252
 *   variable 'feeds_importer_class'. There is only one importer object
1253
 *   per $id system-wide.
1254
 */
1255
function feeds_importer($id) {
1256
  return FeedsConfigurable::instance(variable_get('feeds_importer_class', 'FeedsImporter'), $id);
1257
}
1258

    
1259
/**
1260
 * Gets an instance of a source object.
1261
 *
1262
 * @param string $importer_id
1263
 *   A FeedsImporter id.
1264
 * @param int $feed_nid
1265
 *   The node id of a feed node if the source is attached to a feed node.
1266
 *
1267
 * @return FeedsSource
1268
 *   A FeedsSource object or an object of a class defined by the Drupal
1269
 *   variable 'source_class'.
1270
 */
1271
function feeds_source($importer_id, $feed_nid = 0) {
1272
  return FeedsSource::instance($importer_id, $feed_nid);
1273
}
1274

    
1275
/**
1276
 * Gets an instance of a class for a given plugin and id.
1277
 *
1278
 * @param string $plugin
1279
 *   A string that is the key of the plugin to load.
1280
 * @param string $id
1281
 *   A string that is the id of the object.
1282
 *
1283
 * @return FeedsPlugin
1284
 *   A FeedsPlugin object.
1285
 */
1286
function feeds_plugin($plugin, $id) {
1287
  ctools_include('plugins');
1288

    
1289
  if ($class = ctools_plugin_load_class('feeds', 'plugins', $plugin, 'handler')) {
1290
    return FeedsPlugin::instance($class, $id, ctools_get_plugins('feeds', 'plugins', $plugin));
1291
  }
1292

    
1293
  $args = array('%plugin' => $plugin, '@id' => $id);
1294
  if (user_access('administer feeds')) {
1295
    if (module_exists('feeds_ui')) {
1296
      $args['@link'] = url('admin/structure/feeds/' . $id);
1297
      drupal_set_message(t('Missing Feeds plugin %plugin. See <a href="@link">@id</a>. Check whether all required libraries and modules are installed properly.', $args), 'warning', FALSE);
1298
    }
1299
    else {
1300
      drupal_set_message(t('Missing Feeds plugin %plugin used by the importer "@id". Check whether all required libraries and modules are installed properly. Enable the Feeds Admin UI module to check the importer\'s settings.', $args), 'warning', FALSE);
1301
    }
1302
  }
1303
  else {
1304
    drupal_set_message(t('Missing Feeds plugin %plugin. Please contact your site administrator.', $args), 'warning', FALSE);
1305
  }
1306

    
1307
  $class = ctools_plugin_load_class('feeds', 'plugins', 'FeedsMissingPlugin', 'handler');
1308

    
1309
  return FeedsPlugin::instance($class, $id);
1310
}
1311

    
1312
/**
1313
 * @}
1314
 */
1315

    
1316
/**
1317
 * @defgroup include Funtions for loading libraries
1318
 * @{
1319
 */
1320

    
1321
/**
1322
 * Includes a library file.
1323
 *
1324
 * @param string $file
1325
 *   The filename to load from.
1326
 * @param string $library
1327
 *   The name of the library. If libraries module is installed,
1328
 *   feeds_include_library() will look for libraries with this name managed by
1329
 *   libraries module.
1330
 *
1331
 * @return bool
1332
 *   True if the requested library was found and included with success. False
1333
 *   otherwise.
1334
 */
1335
function feeds_include_library($file, $library) {
1336
  static $included = array();
1337

    
1338
  $key = $library . '/' . $file;
1339

    
1340
  if (!isset($included[$key])) {
1341
    $included[$key] = FALSE;
1342

    
1343
    $library_dir = variable_get('feeds_library_dir', FALSE);
1344
    $feeds_library_path = DRUPAL_ROOT . '/' . drupal_get_path('module', 'feeds') . "/libraries/$file";
1345
    $libraries_path = module_exists('libraries') ? libraries_get_path($library) : FALSE;
1346

    
1347
    // Try first whether libraries module is present and load the file from
1348
    // there. If this fails, require the library from the local path.
1349
    if ($libraries_path && is_file("$libraries_path/$file")) {
1350
      require "$libraries_path/$file";
1351
      $included[$key] = TRUE;
1352
    }
1353
    elseif (is_file(DRUPAL_ROOT . '/sites/all/libraries/' . $key)) {
1354
      require DRUPAL_ROOT . '/sites/all/libraries/' . $key;
1355
      $included[$key] = TRUE;
1356
    }
1357
    elseif ($library_dir && is_file($library_dir . '/' . $key)) {
1358
      require $library_dir . '/' . $key;
1359
      $included[$key] = TRUE;
1360
    }
1361
    elseif (is_file($feeds_library_path)) {
1362
      // @todo: Throws "Deprecated function: Assigning the return value of new
1363
      // by reference is deprecated."
1364
      require $feeds_library_path;
1365
      $included[$key] = TRUE;
1366
    }
1367
  }
1368

    
1369
  return $included[$key];
1370
}
1371

    
1372
/**
1373
 * Checks whether a library is present.
1374
 *
1375
 * @param string $file
1376
 *   The filename to load from.
1377
 * @param string $library
1378
 *   The name of the library. If libraries module is installed,
1379
 *   feeds_library_exists() will look for libraries with this name managed by
1380
 *   libraries module.
1381
 *
1382
 * @return bool
1383
 *   True if the library exists. False otherwise.
1384
 */
1385
function feeds_library_exists($file, $library) {
1386
  $path = module_exists('libraries') ? libraries_get_path($library) : FALSE;
1387
  if ($path && is_file($path . '/' . $file)) {
1388
    return TRUE;
1389
  }
1390
  elseif (is_file(DRUPAL_ROOT . "/sites/all/libraries/$library/$file")) {
1391
    return TRUE;
1392
  }
1393
  elseif (is_file(DRUPAL_ROOT . '/' . drupal_get_path('module', 'feeds') . "/libraries/$file")) {
1394
    return TRUE;
1395
  }
1396
  elseif ($library_dir = variable_get('feeds_library_dir', FALSE)) {
1397
    if (is_file("$library_dir/$library/$file")) {
1398
      return TRUE;
1399
    }
1400
  }
1401

    
1402
  return FALSE;
1403
}
1404

    
1405
/**
1406
 * Checks whether simplepie exists.
1407
 */
1408
function feeds_simplepie_exists() {
1409
  return (
1410
    feeds_library_exists('autoloader.php', 'simplepie') ||
1411
    feeds_library_exists('simplepie.compiled.php', 'simplepie') ||
1412
    feeds_library_exists('simplepie.mini.php', 'simplepie') ||
1413
    feeds_library_exists('simplepie.inc', 'simplepie')
1414
  );
1415
}
1416

    
1417
/**
1418
 * Includes the simplepie library.
1419
 */
1420
function feeds_include_simplepie() {
1421
  $files = array(
1422
    'autoloader.php',
1423
    'simplepie.mini.php',
1424
    'simplepie.compiled.php',
1425
    'simplepie.inc',
1426
  );
1427

    
1428
  foreach ($files as $file) {
1429
    if (feeds_include_library($file, 'simplepie')) {
1430
      return TRUE;
1431
    }
1432
  }
1433

    
1434
  return FALSE;
1435
}
1436

    
1437
/**
1438
 * @}
1439
 */
1440

    
1441
/**
1442
 * Copy of valid_url() that supports the webcal scheme.
1443
 *
1444
 * @see valid_url()
1445
 *
1446
 * @todo Replace with valid_url() when http://drupal.org/node/295021 is fixed.
1447
 */
1448
function feeds_valid_url($url, $absolute = FALSE) {
1449
  if ($absolute) {
1450
    return (bool) preg_match("
1451
      /^                                                      # Start at the beginning of the text
1452
      (?:ftp|https?|feed|webcal):\/\/                         # Look for ftp, http, https, feed or webcal schemes
1453
      (?:                                                     # Userinfo (optional) which is typically
1454
        (?:(?:[\w\.\-\+!$&'\(\)*\+,;=]|%[0-9a-f]{2})+:)*      # a username or a username and password
1455
        (?:[\w\.\-\+%!$&'\(\)*\+,;=]|%[0-9a-f]{2})+@          # combination
1456
      )?
1457
      (?:
1458
        (?:[a-z0-9\-\.]|%[0-9a-f]{2})+                        # A domain name or a IPv4 address
1459
        |(?:\[(?:[0-9a-f]{0,4}:)*(?:[0-9a-f]{0,4})\])         # or a well formed IPv6 address
1460
      )
1461
      (?::[0-9]+)?                                            # Server port number (optional)
1462
      (?:[\/|\?]
1463
        (?:[|\w#!:\.\?\+=&@$'~*,;\/\(\)\[\]\-]|%[0-9a-f]{2})   # The path and query (optional)
1464
      *)?
1465
    $/xi", $url);
1466
  }
1467
  else {
1468
    return (bool) preg_match("/^(?:[\w#!:\.\?\+=&@$'~*,;\/\(\)\[\]\-]|%[0-9a-f]{2})+$/i", $url);
1469
  }
1470
}
1471

    
1472
/**
1473
 * Registers a feed subscription job for execution on feeds_exit().
1474
 *
1475
 * @param array $job
1476
 *   Information about a new job to queue; or if set to NULL (default), leaves
1477
 *   the current queued jobs unchanged.
1478
 *
1479
 * @return array
1480
 *   An array of subscribe jobs to process.
1481
 *
1482
 * @see feeds_exit()
1483
 * @see feeds_get_subscription_jobs()
1484
 */
1485
function feeds_set_subscription_job(array $job = NULL) {
1486
  $jobs = &drupal_static(__FUNCTION__, array());
1487
  if (isset($job)) {
1488
    $jobs[] = $job;
1489
  }
1490
  return $jobs;
1491
}
1492

    
1493
/**
1494
 * Returns the list of queued jobs to be run.
1495
 *
1496
 * @return array
1497
 *   An array of subscribe jobs to process.
1498
 *
1499
 * @see feeds_set_subscription_job()
1500
 */
1501
function feeds_get_subscription_jobs() {
1502
  return feeds_set_subscription_job();
1503
}
1504

    
1505
/**
1506
 * Implements hook_entity_property_info_alter().
1507
 */
1508
function feeds_entity_property_info_alter(&$info) {
1509

    
1510
  foreach ($info as $entity_type => $entity_info) {
1511
    $info[$entity_type]['properties']['feeds_item_guid'] = array(
1512
      'label' => 'Feeds Item GUID',
1513
      'type' => 'text',
1514
      'description' => t('Feeds Item GUID.'),
1515
      'getter callback' => 'feeds_get_feeds_item_property',
1516
    );
1517
    $info[$entity_type]['properties']['feeds_item_url'] = array(
1518
      'label' => 'Feeds Item URL',
1519
      'type' => 'text',
1520
      'description' => t('Feeds Item URL.'),
1521
      'getter callback' => 'feeds_get_feeds_item_property',
1522
    );
1523
    $info[$entity_type]['properties']['feed_nid'] = array(
1524
      'label' => 'Feed NID',
1525
      'type' => 'integer',
1526
      'description' => t('Nid of the Feed Node that imported this entity.'),
1527
      'getter callback' => 'feeds_get_feeds_item_property',
1528
    );
1529
    $info[$entity_type]['properties']['feed_node'] = array(
1530
      'label' => 'Feed node',
1531
      'type' => 'node',
1532
      'description' => t('Feed Node that imported this entity.'),
1533
      'getter callback' => 'feeds_get_feeds_item_property',
1534
    );
1535
  }
1536
}
1537

    
1538
/**
1539
 * Entity API getter callback for getting a feeds_item property on an entity.
1540
 *
1541
 * @param object $entity
1542
 *   An entity object.
1543
 * @param array $options
1544
 *   Options are ignored, but it is a param that is given by the Entiy API.
1545
 * @param string $name
1546
 *   The name of the property to get.
1547
 * @param string $entity_type
1548
 *   The entity's type.
1549
 *
1550
 * @return mixed
1551
 *   The feeds_item property.
1552
 */
1553
function feeds_get_feeds_item_property($entity, array $options, $name, $entity_type) {
1554
  // Map property name to actual feeds_item column.
1555
  $property_map = array(
1556
    'feed_node' => 'feed_nid',
1557
    'feed_nid' => 'feed_nid',
1558
    'feeds_item_guid' => 'guid',
1559
    'feeds_item_url' => 'url',
1560
  );
1561
  $property_name = $property_map[$name];
1562

    
1563
  // First check if the entity has already the feeds item loaded with the
1564
  // requested property and return its value if it does.
1565
  if (isset($entity->feeds_item->$property_name)) {
1566
    return $entity->feeds_item->$property_name;
1567
  }
1568

    
1569
  // No feed item. Try to load the feed item if the entity has an ID.
1570
  list($entity_id) = entity_extract_ids($entity_type, $entity);
1571
  if ($entity_id) {
1572
    $feeds_item = feeds_item_info_load($entity_type, $entity_id);
1573

    
1574
    // Check again for the requested property and return its value if it exists.
1575
    if (isset($feeds_item->$property_name)) {
1576
      return $feeds_item->$property_name;
1577
    }
1578
  }
1579
}
1580

    
1581
/**
1582
 * Implements hook_file_download().
1583
 */
1584
function feeds_file_download($uri) {
1585
  $id = db_query("SELECT id FROM {feeds_source} WHERE source = :uri", array(':uri' => $uri))->fetchField();
1586

    
1587
  if (!$id) {
1588
    // File is not associated with a feed.
1589
    return;
1590
  }
1591

    
1592
  // Get the file record based on the URI. If not in the database just return.
1593
  $files = file_load_multiple(array(), array('uri' => $uri));
1594
  foreach ($files as $item) {
1595
    // Since some database servers sometimes use a case-insensitive comparison
1596
    // by default, double check that the filename is an exact match.
1597
    if ($item->uri === $uri) {
1598
      $file = $item;
1599
      break;
1600
    }
1601
  }
1602
  if (!isset($file)) {
1603
    return;
1604
  }
1605

    
1606
  // Check if this file belongs to Feeds.
1607
  $usage_list = file_usage_list($file);
1608
  if (!isset($usage_list['feeds'])) {
1609
    return;
1610
  }
1611

    
1612
  if (!feeds_access('import', $id)) {
1613
    // User does not have permission to import this feed.
1614
    return -1;
1615
  }
1616

    
1617
  // Return file headers.
1618
  return file_get_content_headers($file);
1619
}
1620

    
1621
/**
1622
 * Feeds API version.
1623
 */
1624
function feeds_api_version() {
1625
  $version = feeds_ctools_plugin_api('feeds', 'plugins');
1626
  return $version['version'];
1627
}