feeds.module

  1. nittany7 modules/contrib/feeds/feeds.module
  2. cis7 modules/contrib/feeds/feeds.module
  3. mooc7 modules/contrib/feeds/feeds.module

Feeds - basic API functions and hook implementations.

Functions

Namesort descending Description
feeds_access Menu access callback.
feeds_alter @todo This needs to be removed and drupal_alter() used. This is crazy dumb.
feeds_api_version Feeds API version.
feeds_batch Batch API worker callback. Used by FeedsSource::startBatchAPIJob().
feeds_cache_clear Resets importer caches. Call when enabling/disabling importers.
feeds_cron Implements hook_cron().
feeds_cron_job_scheduler_info Implements hook_cron_job_scheduler_info().
feeds_cron_queue_info Implements hook_cron_queue_info().
feeds_ctools_plugin_api Implements hook_ctools_plugin_api().
feeds_ctools_plugin_type Implements hook_ctools_plugin_type().
feeds_dbg Logs to a file like /tmp/feeds_my_domain_org.log in temporary directory.
feeds_enabled_importers Gets an array of enabled importer ids.
feeds_entity_delete Implements hook_entity_delete().
feeds_entity_insert Implements hook_entity_insert().
feeds_entity_property_info_alter Implements hook_entity_property_info_alter().
feeds_entity_update Implements hook_entity_update().
feeds_exit Implements hook_exit().
feeds_export Exports a FeedsImporter configuration to code.
feeds_feeds_plugins Implements hook_feeds_plugins().
feeds_field_extra_fields Implements hook_field_extra_fields().
feeds_file_download Implements hook_file_download().
feeds_forms Implements hook_forms().
feeds_form_node_form_alter Implements hook_form_BASE_FORM_ID_alter().
feeds_get_feed_nid Gets the feed_nid for a single entity.
feeds_get_feed_nid_entity_callback Gets the feed_nid for an entity for use in entity metadata.
feeds_get_importer_id Gets an enabled importer configuration by content type.
feeds_get_subscription_jobs Returns the list of queued jobs to be run.
feeds_hook_info Implements hook_hook_info().
feeds_importer Gets an importer instance.
feeds_importer_expire Scheduler callback for expiring content.
feeds_importer_import_access Access callback to determine if the user can import Feeds importers.
feeds_importer_load Menu loader callback.
feeds_importer_load_all Loads all importers.
feeds_importer_title Title callback.
feeds_include_library Includes a library file.
feeds_include_simplepie Includes the simplepie library.
feeds_item_info_insert Inserts an item info object into the feeds_item table.
feeds_item_info_load Loads an item info object.
feeds_item_info_save Inserts or updates an item info object in the feeds_item table.
feeds_library_exists Checks whether a library is present.
feeds_log Writes to feeds log.
feeds_menu Implements hook_menu().
feeds_node_delete Implements hook_node_delete().
feeds_node_insert Implements hook_node_insert().
feeds_node_presave Implements hook_node_presave().
feeds_node_update Implements hook_node_update().
feeds_node_validate Implements hook_node_validate().
feeds_page_access Menu access callback.
feeds_permission Implements feeds_permission().
feeds_plugin Gets an instance of a class for a given plugin and id.
feeds_push_unsubscribe Scheduler callback for unsubscribing from PuSH hubs.
feeds_reschedule Reschedule one or all importers.
feeds_set_subscription_job Registers a feed subscription job for execution on feeds_exit().
feeds_simplepie_exists Checks whether simplepie exists.
feeds_source Gets an instance of a source object.
feeds_source_clear Scheduler callback for deleting all items from a source.
feeds_source_import Scheduler callback for importing from a source.
feeds_theme Implements hook_theme().
feeds_valid_url Copy of valid_url() that supports the webcal scheme.
feeds_views_api Implements hook_views_api().
_feeds_importer_digest Helper function for feeds_get_importer_id() and feeds_enabled_importers().

Constants

File

modules/contrib/feeds/feeds.module
View source
  1. <?php
  2. /**
  3. * @file
  4. * Feeds - basic API functions and hook implementations.
  5. */
  6. // Common request time, use as point of reference and to avoid calls to time().
  7. define('FEEDS_REQUEST_TIME', time());
  8. // Do not schedule a feed for refresh.
  9. define('FEEDS_SCHEDULE_NEVER', -1);
  10. // Never expire feed items.
  11. define('FEEDS_EXPIRE_NEVER', -1);
  12. // An object that is not persistent. Compare EXPORT_IN_DATABASE, EXPORT_IN_CODE.
  13. define('FEEDS_EXPORT_NONE', 0x0);
  14. // Status of batched operations.
  15. define('FEEDS_BATCH_COMPLETE', 1.0);
  16. define('FEEDS_BATCH_ACTIVE', 0.0);
  17. /**
  18. * @defgroup hooks Hook and callback implementations
  19. * @{
  20. */
  21. /**
  22. * Implements hook_hook_info().
  23. */
  24. function feeds_hook_info() {
  25. $hooks = array(
  26. 'feeds_plugins',
  27. 'feeds_after_parse',
  28. 'feeds_before_import',
  29. 'feeds_before_update',
  30. 'feeds_presave',
  31. 'feeds_after_save',
  32. 'feeds_after_import',
  33. 'feeds_after_clear',
  34. 'feeds_processor_targets_alter',
  35. 'feeds_parser_sources_alter',
  36. );
  37. return array_fill_keys($hooks, array('group' => 'feeds'));
  38. }
  39. /**
  40. * Implements hook_cron().
  41. */
  42. function feeds_cron() {
  43. if ($importers = feeds_reschedule()) {
  44. foreach ($importers as $id) {
  45. feeds_importer($id)->schedule();
  46. $rows = db_query("SELECT feed_nid FROM {feeds_source} WHERE id = :id", array(':id' => $id));
  47. foreach ($rows as $row) {
  48. feeds_source($id, $row->feed_nid)->schedule();
  49. }
  50. }
  51. feeds_reschedule(FALSE);
  52. }
  53. // Expire old log entries.
  54. db_delete('feeds_log')
  55. ->condition('request_time', REQUEST_TIME - 604800, '<')
  56. ->execute();
  57. }
  58. /**
  59. * Implements hook_cron_job_scheduler_info().
  60. *
  61. * Compare queue names with key names in feeds_cron_queue_info().
  62. */
  63. function feeds_cron_job_scheduler_info() {
  64. $info = array();
  65. $info['feeds_source_import'] = array(
  66. 'queue name' => 'feeds_source_import',
  67. );
  68. $info['feeds_source_clear'] = array(
  69. 'queue name' => 'feeds_source_clear',
  70. );
  71. $info['feeds_importer_expire'] = array(
  72. 'queue name' => 'feeds_importer_expire',
  73. );
  74. $info['feeds_push_unsubscribe'] = array(
  75. 'queue name' => 'feeds_push_unsubscribe',
  76. );
  77. return $info;
  78. }
  79. /**
  80. * Implements hook_cron_queue_info().
  81. */
  82. function feeds_cron_queue_info() {
  83. $queues = array();
  84. $queues['feeds_source_import'] = array(
  85. 'worker callback' => 'feeds_source_import',
  86. 'time' => 15,
  87. );
  88. $queues['feeds_source_clear'] = array(
  89. 'worker callback' => 'feeds_source_clear',
  90. 'time' => 15,
  91. );
  92. $queues['feeds_importer_expire'] = array(
  93. 'worker callback' => 'feeds_importer_expire',
  94. 'time' => 15,
  95. );
  96. $queues['feeds_push_unsubscribe'] = array(
  97. 'worker callback' => 'feeds_push_unsubscribe',
  98. 'time' => 15,
  99. );
  100. return $queues;
  101. }
  102. /**
  103. * Scheduler callback for importing from a source.
  104. */
  105. function feeds_source_import($job) {
  106. $source = feeds_source($job['type'], $job['id']);
  107. try {
  108. $source->existing()->import();
  109. }
  110. catch (FeedsNotExistingException $e) {
  111. // Do nothing.
  112. }
  113. catch (Exception $e) {
  114. $source->log('import', $e->getMessage(), array(), WATCHDOG_ERROR);
  115. }
  116. $source->scheduleImport();
  117. }
  118. /**
  119. * Scheduler callback for deleting all items from a source.
  120. */
  121. function feeds_source_clear($job) {
  122. $source = feeds_source($job['type'], $job['id']);
  123. try {
  124. $source->existing()->clear();
  125. }
  126. catch (FeedsNotExistingException $e) {
  127. // Do nothing.
  128. }
  129. catch (Exception $e) {
  130. $source->log('clear', $e->getMessage(), array(), WATCHDOG_ERROR);
  131. }
  132. $source->scheduleClear();
  133. }
  134. /**
  135. * Scheduler callback for expiring content.
  136. */
  137. function feeds_importer_expire($job) {
  138. $importer = feeds_importer($job['type']);
  139. try {
  140. $importer->existing()->expire();
  141. }
  142. catch (FeedsNotExistingException $e) {
  143. // Do nothing.
  144. }
  145. catch (Exception $e) {
  146. $importer->log('expire', $e->getMessage(), array(), WATCHDOG_ERROR);
  147. }
  148. $importer->scheduleExpire();
  149. }
  150. /**
  151. * Scheduler callback for unsubscribing from PuSH hubs.
  152. */
  153. function feeds_push_unsubscribe($job) {
  154. $source = feeds_source($job['type'], $job['id']);
  155. $fetcher = feeds_plugin('FeedsHTTPFetcher', $source->importer->id);
  156. $fetcher->unsubscribe($source);
  157. }
  158. /**
  159. * Batch API worker callback. Used by FeedsSource::startBatchAPIJob().
  160. *
  161. * @see FeedsSource::startBatchAPIJob().
  162. *
  163. * @todo Harmonize Job Scheduler API callbacks with Batch API callbacks?
  164. *
  165. * @param $method
  166. * Method to execute on importer; one of 'import' or 'clear'.
  167. * @param $importer_id
  168. * Identifier of a FeedsImporter object.
  169. * @param $feed_nid
  170. * If importer is attached to content type, feed node id identifying the
  171. * source to be imported.
  172. * @param $context
  173. * Batch context.
  174. */
  175. function feeds_batch($method, $importer_id, $feed_nid = 0, &$context) {
  176. $context['finished'] = FEEDS_BATCH_COMPLETE;
  177. try {
  178. $context['finished'] = feeds_source($importer_id, $feed_nid)->$method();
  179. }
  180. catch (Exception $e) {
  181. drupal_set_message($e->getMessage(), 'error');
  182. }
  183. }
  184. /**
  185. * Reschedule one or all importers.
  186. *
  187. * @param $importer_id
  188. * If TRUE, all importers will be rescheduled, if FALSE, no importers will
  189. * be rescheduled, if an importer id, only importer of that id will be
  190. * rescheduled.
  191. *
  192. * @return
  193. * TRUE if all importers need rescheduling. FALSE if no rescheduling is
  194. * required. An array of importers that need rescheduling.
  195. */
  196. function feeds_reschedule($importer_id = NULL) {
  197. $reschedule = variable_get('feeds_reschedule', FALSE);
  198. if ($importer_id === TRUE || $importer_id === FALSE) {
  199. $reschedule = $importer_id;
  200. }
  201. elseif (is_string($importer_id) && $reschedule !== TRUE) {
  202. $reschedule = is_array($reschedule) ? $reschedule : array();
  203. $reschedule[$importer_id] = $importer_id;
  204. }
  205. variable_set('feeds_reschedule', $reschedule);
  206. if ($reschedule === TRUE) {
  207. return feeds_enabled_importers();
  208. }
  209. return $reschedule;
  210. }
  211. /**
  212. * Implements feeds_permission().
  213. */
  214. function feeds_permission() {
  215. $perms = array(
  216. 'administer feeds' => array(
  217. 'title' => t('Administer Feeds'),
  218. 'description' => t('Create, update, delete importers, execute import and delete tasks on any importer.')
  219. ),
  220. );
  221. foreach (feeds_importer_load_all() as $importer) {
  222. $perms["import $importer->id feeds"] = array(
  223. 'title' => t('Import @name feeds', array('@name' => $importer->config['name'])),
  224. );
  225. $perms["clear $importer->id feeds"] = array(
  226. 'title' => t('Delete items from @name feeds', array('@name' => $importer->config['name'])),
  227. );
  228. $perms["unlock $importer->id feeds"] = array(
  229. 'title' => t('Unlock imports from @name feeds', array('@name' => $importer->config['name'])),
  230. 'description' => t('If a feed importation breaks for some reason, users with this permission can unlock them.')
  231. );
  232. }
  233. return $perms;
  234. }
  235. /**
  236. * Implements hook_forms().
  237. *
  238. * Declare form callbacks for all known classes derived from FeedsConfigurable.
  239. */
  240. function feeds_forms() {
  241. $forms = array();
  242. $forms['FeedsImporter_feeds_form']['callback'] = 'feeds_form';
  243. $plugins = FeedsPlugin::all();
  244. foreach ($plugins as $plugin) {
  245. $forms[$plugin['handler']['class'] . '_feeds_form']['callback'] = 'feeds_form';
  246. }
  247. return $forms;
  248. }
  249. /**
  250. * Implements hook_menu().
  251. */
  252. function feeds_menu() {
  253. $items = array();
  254. $items['import'] = array(
  255. 'title' => 'Import',
  256. 'page callback' => 'feeds_page',
  257. 'access callback' => 'feeds_page_access',
  258. 'file' => 'feeds.pages.inc',
  259. );
  260. $items['import/%'] = array(
  261. 'title callback' => 'feeds_importer_title',
  262. 'title arguments' => array(1),
  263. 'page callback' => 'drupal_get_form',
  264. 'page arguments' => array('feeds_import_form', 1),
  265. 'access callback' => 'feeds_access',
  266. 'access arguments' => array('import', 1),
  267. 'file' => 'feeds.pages.inc',
  268. );
  269. $items['import/%/import'] = array(
  270. 'title' => 'Import',
  271. 'type' => MENU_DEFAULT_LOCAL_TASK,
  272. 'weight' => -10,
  273. );
  274. $items['import/%/delete-items'] = array(
  275. 'title' => 'Delete items',
  276. 'page callback' => 'drupal_get_form',
  277. 'page arguments' => array('feeds_delete_tab_form', 1),
  278. 'access callback' => 'feeds_access',
  279. 'access arguments' => array('clear', 1),
  280. 'file' => 'feeds.pages.inc',
  281. 'type' => MENU_LOCAL_TASK,
  282. );
  283. $items['import/%/unlock'] = array(
  284. 'title' => 'Unlock',
  285. 'page callback' => 'drupal_get_form',
  286. 'page arguments' => array('feeds_unlock_tab_form', 1),
  287. 'access callback' => 'feeds_access',
  288. 'access arguments' => array('unlock', 1),
  289. 'file' => 'feeds.pages.inc',
  290. 'type' => MENU_LOCAL_TASK,
  291. );
  292. $items['import/%/template'] = array(
  293. 'page callback' => 'feeds_importer_template',
  294. 'page arguments' => array(1),
  295. 'access callback' => 'feeds_access',
  296. 'access arguments' => array('import', 1),
  297. 'file' => 'feeds.pages.inc',
  298. 'type' => MENU_CALLBACK,
  299. );
  300. $items['node/%node/import'] = array(
  301. 'title' => 'Import',
  302. 'page callback' => 'drupal_get_form',
  303. 'page arguments' => array('feeds_import_tab_form', 1),
  304. 'access callback' => 'feeds_access',
  305. 'access arguments' => array('import', 1),
  306. 'file' => 'feeds.pages.inc',
  307. 'type' => MENU_LOCAL_TASK,
  308. 'weight' => 10,
  309. );
  310. $items['node/%node/delete-items'] = array(
  311. 'title' => 'Delete items',
  312. 'page callback' => 'drupal_get_form',
  313. 'page arguments' => array('feeds_delete_tab_form', NULL, 1),
  314. 'access callback' => 'feeds_access',
  315. 'access arguments' => array('clear', 1),
  316. 'file' => 'feeds.pages.inc',
  317. 'type' => MENU_LOCAL_TASK,
  318. 'weight' => 11,
  319. );
  320. $items['node/%node/unlock'] = array(
  321. 'title' => 'Unlock',
  322. 'page callback' => 'drupal_get_form',
  323. 'page arguments' => array('feeds_unlock_tab_form', NULL, 1),
  324. 'access callback' => 'feeds_access',
  325. 'access arguments' => array('unlock', 1),
  326. 'file' => 'feeds.pages.inc',
  327. 'type' => MENU_LOCAL_TASK,
  328. 'weight' => 11,
  329. );
  330. // @todo Eliminate this step and thus eliminate clearing menu cache when
  331. // manipulating importers.
  332. foreach (feeds_importer_load_all() as $importer) {
  333. $items += $importer->fetcher->menuItem();
  334. }
  335. return $items;
  336. }
  337. /**
  338. * Menu loader callback.
  339. */
  340. function feeds_importer_load($id) {
  341. return feeds_importer($id);
  342. }
  343. /**
  344. * Title callback.
  345. */
  346. function feeds_importer_title($id) {
  347. $importer = feeds_importer($id);
  348. return $importer->config['name'];
  349. }
  350. /**
  351. * Implements hook_theme().
  352. */
  353. function feeds_theme() {
  354. return array(
  355. 'feeds_upload' => array(
  356. 'file' => 'feeds.pages.inc',
  357. 'render element' => 'element',
  358. ),
  359. 'feeds_source_status' => array(
  360. 'file' => 'feeds.pages.inc',
  361. 'variables' => array(
  362. 'progress_importing' => NULL,
  363. 'progress_clearing' => NULL,
  364. 'imported' => NULL,
  365. 'count' => NULL,
  366. ),
  367. ),
  368. );
  369. }
  370. /**
  371. * Menu access callback.
  372. *
  373. * @param $action
  374. * The action to be performed. Possible values are:
  375. * - import
  376. * - clear
  377. * - unlock
  378. * @param $param
  379. * Node object or FeedsImporter id.
  380. */
  381. function feeds_access($action, $param) {
  382. if (!in_array($action, array('import', 'clear', 'unlock'))) {
  383. // If $action is not one of the supported actions, we return access denied.
  384. return FALSE;
  385. }
  386. if (is_string($param)) {
  387. $importer_id = $param;
  388. }
  389. elseif ($param->type) {
  390. $importer_id = feeds_get_importer_id($param->type);
  391. }
  392. // Check for permissions if feed id is present, otherwise return FALSE.
  393. if ($importer_id) {
  394. if (user_access('administer feeds') || user_access("{$action} {$importer_id} feeds")) {
  395. return TRUE;
  396. }
  397. }
  398. return FALSE;
  399. }
  400. /**
  401. * Access callback to determine if the user can import Feeds importers.
  402. *
  403. * Feeds imports require an additional access check because they are PHP
  404. * code and PHP is more locked down than administer feeds.
  405. */
  406. function feeds_importer_import_access() {
  407. return user_access('administer feeds') && user_access('use PHP for settings');
  408. }
  409. /**
  410. * Menu access callback.
  411. */
  412. function feeds_page_access() {
  413. if (user_access('administer feeds')) {
  414. return TRUE;
  415. }
  416. foreach (feeds_enabled_importers() as $id) {
  417. if (user_access("import $id feeds")) {
  418. return TRUE;
  419. }
  420. }
  421. return FALSE;
  422. }
  423. /**
  424. * Implements hook_exit().
  425. */
  426. function feeds_exit() {
  427. // Process any pending PuSH subscriptions.
  428. $jobs = feeds_get_subscription_jobs();
  429. foreach ($jobs as $job) {
  430. if (!isset($job['fetcher']) || !isset($job['source'])) {
  431. continue;
  432. }
  433. $job['fetcher']->subscribe($job['source']);
  434. }
  435. if (drupal_static('feeds_log_error', FALSE)) {
  436. watchdog('feeds', 'Feeds reported errors, visit the Feeds log for details.', array(), WATCHDOG_ERROR, 'admin/reports/dblog/feeds');
  437. }
  438. }
  439. /**
  440. * Implements hook_views_api().
  441. */
  442. function feeds_views_api() {
  443. return array(
  444. 'api' => 3,
  445. 'path' => drupal_get_path('module', 'feeds') . '/views',
  446. );
  447. }
  448. /**
  449. * Implements hook_ctools_plugin_api().
  450. */
  451. function feeds_ctools_plugin_api($owner, $api) {
  452. if ($owner == 'feeds' && $api == 'plugins') {
  453. return array('version' => 1);
  454. }
  455. }
  456. /**
  457. * Implements hook_ctools_plugin_type().
  458. */
  459. function feeds_ctools_plugin_type() {
  460. return array(
  461. 'plugins' => array(
  462. 'cache' => TRUE,
  463. 'use hooks' => TRUE,
  464. 'classes' => array('handler'),
  465. ),
  466. );
  467. }
  468. /**
  469. * Implements hook_feeds_plugins().
  470. */
  471. function feeds_feeds_plugins() {
  472. module_load_include('inc', 'feeds', 'feeds.plugins');
  473. return _feeds_feeds_plugins();
  474. }
  475. /**
  476. * Gets the feed_nid for a single entity.
  477. *
  478. * @param int $entity_id
  479. * The entity id.
  480. * @param string $entity_type
  481. * The type of entity.
  482. *
  483. * @return int|bool
  484. * The feed_nid of the entity, or FALSE if the entity doesn't belong to a
  485. * feed.
  486. */
  487. function feeds_get_feed_nid($entity_id, $entity_type) {
  488. 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();
  489. }
  490. /**
  491. * Implements hook_entity_insert().
  492. */
  493. function feeds_entity_insert($entity, $type) {
  494. list($id) = entity_extract_ids($type, $entity);
  495. feeds_item_info_insert($entity, $id);
  496. }
  497. /**
  498. * Implements hook_entity_update().
  499. */
  500. function feeds_entity_update($entity, $type) {
  501. list($id) = entity_extract_ids($type, $entity);
  502. feeds_item_info_save($entity, $id);
  503. }
  504. /**
  505. * Implements hook_entity_delete().
  506. */
  507. function feeds_entity_delete($entity, $type) {
  508. list($id) = entity_extract_ids($type, $entity);
  509. // Delete any imported items produced by the source.
  510. db_delete('feeds_item')
  511. ->condition('entity_type', $type)
  512. ->condition('entity_id', $id)
  513. ->execute();
  514. }
  515. /**
  516. * Implements hook_node_validate().
  517. */
  518. function feeds_node_validate($node, $form, &$form_state) {
  519. if (!$importer_id = feeds_get_importer_id($node->type)) {
  520. return;
  521. }
  522. // Keep a copy of the title for subsequent node creation stages.
  523. // @todo: revisit whether $node still looses all of its properties
  524. // between validate and insert stage.
  525. $last_title = &drupal_static('feeds_node_last_title');
  526. $last_feeds = &drupal_static('feeds_node_last_feeds');
  527. // On validation stage we are working with a FeedsSource object that is
  528. // not tied to a nid - when creating a new node there is no
  529. // $node->nid at this stage.
  530. $source = feeds_source($importer_id);
  531. // Node module magically moved $form['feeds'] to $node->feeds :P.
  532. // configFormValidate may modify $last_feed, smuggle it to update/insert stage
  533. // through a static variable.
  534. $last_feeds = $node->feeds;
  535. $source->configFormValidate($last_feeds);
  536. // If node title is empty, try to retrieve title from feed.
  537. if (trim($node->title) == '') {
  538. try {
  539. $source->addConfig($last_feeds);
  540. if (!$last_title = $source->preview()->title) {
  541. throw new Exception();
  542. }
  543. }
  544. catch (Exception $e) {
  545. drupal_set_message($e->getMessage(), 'error');
  546. form_set_error('title', t('Could not retrieve title from feed.'));
  547. }
  548. }
  549. }
  550. /**
  551. * Implements hook_node_presave().
  552. */
  553. function feeds_node_presave($node) {
  554. // Populate $node->title and $node->feed from result of validation phase.
  555. $last_title = &drupal_static('feeds_node_last_title');
  556. $last_feeds = &drupal_static('feeds_node_last_feeds');
  557. if (empty($node->title) && !empty($last_title)) {
  558. $node->title = $last_title;
  559. }
  560. if (!empty($last_feeds)) {
  561. $node->feeds = $last_feeds;
  562. }
  563. $last_title = NULL;
  564. $last_feeds = NULL;
  565. }
  566. /**
  567. * Implements hook_node_insert().
  568. */
  569. function feeds_node_insert($node) {
  570. // Source attached to node.
  571. feeds_node_update($node);
  572. if (isset($node->feeds) && $importer_id = feeds_get_importer_id($node->type)) {
  573. $source = feeds_source($importer_id, $node->nid);
  574. // Start import if requested.
  575. if (feeds_importer($importer_id)->config['import_on_create'] && !isset($node->feeds['suppress_import'])) {
  576. $source->startImport();
  577. }
  578. // Schedule source and importer.
  579. $source->schedule();
  580. feeds_importer($importer_id)->schedule();
  581. }
  582. }
  583. /**
  584. * Implements hook_node_update().
  585. */
  586. function feeds_node_update($node) {
  587. // Source attached to node.
  588. if (isset($node->feeds) && $importer_id = feeds_get_importer_id($node->type)) {
  589. $source = feeds_source($importer_id, $node->nid);
  590. $source->addConfig($node->feeds);
  591. $source->save();
  592. }
  593. }
  594. /**
  595. * Implements hook_node_delete().
  596. */
  597. function feeds_node_delete($node) {
  598. // Source attached to node.
  599. // Make sure we don't leave any orphans behind: Do not use
  600. // feeds_get_importer_id() to determine importer id as the importer may have
  601. // been deleted.
  602. if ($importer_id = db_query("SELECT id FROM {feeds_source} WHERE feed_nid = :nid", array(':nid' => $node->nid))->fetchField()) {
  603. feeds_source($importer_id, $node->nid)->delete();
  604. }
  605. }
  606. /**
  607. * Implements hook_form_BASE_FORM_ID_alter().
  608. */
  609. function feeds_form_node_form_alter(&$form, $form_state) {
  610. if ($importer_id = feeds_get_importer_id($form['#node']->type)) {
  611. // Set title to not required, try to retrieve it from feed.
  612. if (isset($form['title'])) {
  613. $form['title']['#required'] = FALSE;
  614. }
  615. // Enable uploads.
  616. $form['#attributes']['enctype'] = 'multipart/form-data';
  617. // Build form.
  618. $source = feeds_source($importer_id, empty($form['#node']->nid) ? 0 : $form['#node']->nid);
  619. $form['feeds'] = array(
  620. '#type' => 'fieldset',
  621. '#title' => t('Feed'),
  622. '#tree' => TRUE,
  623. '#weight' => 0,
  624. );
  625. $form['feeds'] += $source->configForm($form_state);
  626. $form['#feed_id'] = $importer_id;
  627. }
  628. }
  629. /**
  630. * Implements hook_field_extra_fields().
  631. */
  632. function feeds_field_extra_fields() {
  633. $extras = array();
  634. foreach (node_type_get_names() as $type => $name) {
  635. if (feeds_get_importer_id($type)) {
  636. $extras['node'][$type]['form']['feeds'] = array(
  637. 'label' => t('Feed'),
  638. 'description' => t('Feeds module form elements'),
  639. 'weight' => 0,
  640. );
  641. }
  642. }
  643. return $extras;
  644. }
  645. /**
  646. * @}
  647. */
  648. /**
  649. * @defgroup utility Utility functions
  650. * @{
  651. */
  652. /**
  653. * Loads all importers.
  654. *
  655. * @param $load_disabled
  656. * Pass TRUE to load all importers, enabled or disabled, pass FALSE to only
  657. * retrieve enabled importers.
  658. *
  659. * @return
  660. * An array of all feed configurations available.
  661. */
  662. function feeds_importer_load_all($load_disabled = FALSE) {
  663. $feeds = array();
  664. // This function can get called very early in install process through
  665. // menu_router_rebuild(). Do not try to include CTools if not available.
  666. if (function_exists('ctools_include')) {
  667. ctools_include('export');
  668. $configs = ctools_export_load_object('feeds_importer', 'all');
  669. foreach ($configs as $config) {
  670. if (!empty($config->id) && ($load_disabled || empty($config->disabled))) {
  671. $feeds[$config->id] = feeds_importer($config->id);
  672. }
  673. }
  674. }
  675. return $feeds;
  676. }
  677. /**
  678. * Gets an array of enabled importer ids.
  679. *
  680. * @return
  681. * An array where the values contain ids of enabled importers.
  682. */
  683. function feeds_enabled_importers() {
  684. return array_keys(_feeds_importer_digest());
  685. }
  686. /**
  687. * Gets an enabled importer configuration by content type.
  688. *
  689. * @param $content_type
  690. * A node type string.
  691. *
  692. * @return
  693. * A FeedsImporter id if there is an importer for the given content type,
  694. * FALSE otherwise.
  695. */
  696. function feeds_get_importer_id($content_type) {
  697. $importers = array_flip(_feeds_importer_digest());
  698. return isset($importers[$content_type]) ? $importers[$content_type] : FALSE;
  699. }
  700. /**
  701. * Helper function for feeds_get_importer_id() and feeds_enabled_importers().
  702. */
  703. function _feeds_importer_digest() {
  704. $importers = &drupal_static(__FUNCTION__);
  705. if ($importers === NULL) {
  706. if ($cache = cache_get(__FUNCTION__)) {
  707. $importers = $cache->data;
  708. }
  709. else {
  710. $importers = array();
  711. foreach (feeds_importer_load_all() as $importer) {
  712. $importers[$importer->id] = isset($importer->config['content_type']) ? $importer->config['content_type'] : '';
  713. }
  714. cache_set(__FUNCTION__, $importers);
  715. }
  716. }
  717. return $importers;
  718. }
  719. /**
  720. * Resets importer caches. Call when enabling/disabling importers.
  721. */
  722. function feeds_cache_clear($rebuild_menu = TRUE) {
  723. cache_clear_all('_feeds_importer_digest', 'cache');
  724. drupal_static_reset('_feeds_importer_digest');
  725. ctools_include('export');
  726. ctools_export_load_object_reset('feeds_importer');
  727. drupal_static_reset('_node_types_build');
  728. if ($rebuild_menu) {
  729. menu_rebuild();
  730. }
  731. }
  732. /**
  733. * Exports a FeedsImporter configuration to code.
  734. */
  735. function feeds_export($importer_id, $indent = '') {
  736. ctools_include('export');
  737. $result = ctools_export_load_object('feeds_importer', 'names', array('id' => $importer_id));
  738. if (isset($result[$importer_id])) {
  739. return ctools_export_object('feeds_importer', $result[$importer_id], $indent);
  740. }
  741. }
  742. /**
  743. * Logs to a file like /tmp/feeds_my_domain_org.log in temporary directory.
  744. */
  745. function feeds_dbg($msg) {
  746. if (variable_get('feeds_debug', FALSE)) {
  747. if (!is_string($msg)) {
  748. $msg = var_export($msg, TRUE);
  749. }
  750. $filename = trim(str_replace('/', '_', $_SERVER['HTTP_HOST'] . base_path()), '_');
  751. $handle = fopen("temporary://feeds_$filename.log", 'a');
  752. fwrite($handle, gmdate('c') . "\t$msg\n");
  753. fclose($handle);
  754. }
  755. }
  756. /**
  757. * Writes to feeds log.
  758. */
  759. function feeds_log($importer_id, $feed_nid, $type, $message, $variables = array(), $severity = WATCHDOG_NOTICE) {
  760. if ($severity < WATCHDOG_NOTICE) {
  761. $error = &drupal_static('feeds_log_error', FALSE);
  762. $error = TRUE;
  763. }
  764. db_insert('feeds_log')
  765. ->fields(array(
  766. 'id' => $importer_id,
  767. 'feed_nid' => $feed_nid,
  768. 'log_time' => time(),
  769. 'request_time' => REQUEST_TIME,
  770. 'type' => $type,
  771. 'message' => $message,
  772. 'variables' => serialize($variables),
  773. 'severity' => $severity,
  774. ))
  775. ->execute();
  776. }
  777. /**
  778. * Loads an item info object.
  779. *
  780. * Example usage:
  781. *
  782. * $info = feeds_item_info_load('node', $node->nid);
  783. */
  784. function feeds_item_info_load($entity_type, $entity_id) {
  785. return db_select('feeds_item')
  786. ->fields('feeds_item')
  787. ->condition('entity_type', $entity_type)
  788. ->condition('entity_id', $entity_id)
  789. ->execute()
  790. ->fetchObject();
  791. }
  792. /**
  793. * Inserts an item info object into the feeds_item table.
  794. */
  795. function feeds_item_info_insert($entity, $entity_id) {
  796. if (isset($entity->feeds_item)) {
  797. $entity->feeds_item->entity_id = $entity_id;
  798. drupal_write_record('feeds_item', $entity->feeds_item);
  799. }
  800. }
  801. /**
  802. * Inserts or updates an item info object in the feeds_item table.
  803. */
  804. function feeds_item_info_save($entity, $entity_id) {
  805. if (isset($entity->feeds_item)) {
  806. $entity->feeds_item->entity_id = $entity_id;
  807. if (feeds_item_info_load($entity->feeds_item->entity_type, $entity_id)) {
  808. drupal_write_record('feeds_item', $entity->feeds_item, array('entity_type', 'entity_id'));
  809. }
  810. else {
  811. feeds_item_info_insert($entity, $entity_id);
  812. }
  813. }
  814. }
  815. /**
  816. * @}
  817. */
  818. /**
  819. * @defgroup instantiators Instantiators
  820. * @{
  821. */
  822. /**
  823. * Gets an importer instance.
  824. *
  825. * @param $id
  826. * The unique id of the importer object.
  827. *
  828. * @return
  829. * A FeedsImporter object or an object of a class defined by the Drupal
  830. * variable 'feeds_importer_class'. There is only one importer object
  831. * per $id system-wide.
  832. */
  833. function feeds_importer($id) {
  834. return FeedsConfigurable::instance(variable_get('feeds_importer_class', 'FeedsImporter'), $id);
  835. }
  836. /**
  837. * Gets an instance of a source object.
  838. *
  839. * @param $importer_id
  840. * A FeedsImporter id.
  841. * @param $feed_nid
  842. * The node id of a feed node if the source is attached to a feed node.
  843. *
  844. * @return
  845. * A FeedsSource object or an object of a class defiend by the Drupal
  846. * variable 'source_class'.
  847. */
  848. function feeds_source($importer_id, $feed_nid = 0) {
  849. return FeedsSource::instance($importer_id, $feed_nid);
  850. }
  851. /**
  852. * Gets an instance of a class for a given plugin and id.
  853. *
  854. * @param $plugin
  855. * A string that is the key of the plugin to load.
  856. * @param $id
  857. * A string that is the id of the object.
  858. *
  859. * @return
  860. * A FeedsPlugin object.
  861. *
  862. * @throws Exception
  863. * If plugin can't be instantiated.
  864. */
  865. function feeds_plugin($plugin, $id) {
  866. ctools_include('plugins');
  867. if ($class = ctools_plugin_load_class('feeds', 'plugins', $plugin, 'handler')) {
  868. return FeedsConfigurable::instance($class, $id);
  869. }
  870. $args = array('%plugin' => $plugin, '@id' => $id);
  871. if (user_access('administer feeds')) {
  872. $args['@link'] = url('admin/structure/feeds/' . $id);
  873. 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);
  874. }
  875. else {
  876. drupal_set_message(t('Missing Feeds plugin %plugin. Please contact your site administrator.', $args), 'warning', FALSE);
  877. }
  878. $class = ctools_plugin_load_class('feeds', 'plugins', 'FeedsMissingPlugin', 'handler');
  879. return FeedsConfigurable::instance($class, $id);
  880. }
  881. /**
  882. * @}
  883. */
  884. /**
  885. * @defgroup include Funtions for loading libraries
  886. * @{
  887. */
  888. /**
  889. * Includes a library file.
  890. *
  891. * @param $file
  892. * The filename to load from.
  893. * @param $library
  894. * The name of the library. If libraries module is installed,
  895. * feeds_include_library() will look for libraries with this name managed by
  896. * libraries module.
  897. */
  898. function feeds_include_library($file, $library) {
  899. static $included = array();
  900. static $ignore_deprecated = array('simplepie');
  901. if (!isset($included[$file])) {
  902. // Disable deprecated warning for libraries known for throwing them
  903. if (in_array($library, $ignore_deprecated)) {
  904. $level = error_reporting();
  905. // We can safely use E_DEPRECATED since Drupal 7 requires PHP 5.3+
  906. error_reporting($level ^ E_DEPRECATED ^ E_STRICT);
  907. }
  908. $library_dir = variable_get('feeds_library_dir', FALSE);
  909. $feeds_library_path = DRUPAL_ROOT . '/' . drupal_get_path('module', 'feeds') . "/libraries/$file";
  910. // Try first whether libraries module is present and load the file from
  911. // there. If this fails, require the library from the local path.
  912. if (module_exists('libraries') && file_exists(libraries_get_path($library) . "/$file")) {
  913. require libraries_get_path($library) . "/$file";
  914. $included[$file] = TRUE;
  915. }
  916. elseif ($library_dir && file_exists("$library_dir/$library/$file")) {
  917. require "$library_dir/$library/$file";
  918. $included[$file] = TRUE;
  919. }
  920. elseif (file_exists($feeds_library_path)) {
  921. // @todo: Throws "Deprecated function: Assigning the return value of new
  922. // by reference is deprecated."
  923. require $feeds_library_path;
  924. $included[$file] = TRUE;
  925. }
  926. // Restore error reporting level
  927. if (isset($level)) {
  928. error_reporting($level);
  929. }
  930. }
  931. if (isset($included[$file])) {
  932. return TRUE;
  933. }
  934. return FALSE;
  935. }
  936. /**
  937. * Checks whether a library is present.
  938. *
  939. * @param $file
  940. * The filename to load from.
  941. * @param $library
  942. * The name of the library. If libraries module is installed,
  943. * feeds_library_exists() will look for libraries with this name managed by
  944. * libraries module.
  945. */
  946. function feeds_library_exists($file, $library) {
  947. if (module_exists('libraries') && file_exists(libraries_get_path($library) . "/$file")) {
  948. return TRUE;
  949. }
  950. elseif (file_exists(DRUPAL_ROOT . '/' . drupal_get_path('module', 'feeds') . "/libraries/$file")) {
  951. return TRUE;
  952. }
  953. elseif ($library_dir = variable_get('feeds_library_dir', FALSE)) {
  954. if (file_exists("$library_dir/$library/$file")) {
  955. return TRUE;
  956. }
  957. }
  958. return FALSE;
  959. }
  960. /**
  961. * Checks whether simplepie exists.
  962. */
  963. function feeds_simplepie_exists() {
  964. return (feeds_library_exists('simplepie.compiled.php', 'simplepie') ||
  965. feeds_library_exists('simplepie.mini.php', 'simplepie') ||
  966. feeds_library_exists('simplepie.inc', 'simplepie')
  967. );
  968. }
  969. /**
  970. * Includes the simplepie library.
  971. */
  972. function feeds_include_simplepie() {
  973. $files = array('simplepie.mini.php', 'simplepie.compiled.php', 'simplepie.inc');
  974. foreach ($files as $file) {
  975. if (feeds_include_library($file, 'simplepie')) {
  976. return TRUE;
  977. }
  978. }
  979. return FALSE;
  980. }
  981. /**
  982. * @deprecated
  983. *
  984. * Simplified drupal_alter().
  985. *
  986. * - None of that 'multiple parameters by ref' crazyness.
  987. * - Don't use module_implements() to allow hot including on behalf
  988. * implementations (see mappers/).
  989. *
  990. * @todo This needs to be removed and drupal_alter() used. This is crazy dumb.
  991. */
  992. function feeds_alter($type, &$data) {
  993. $args = array(&$data);
  994. $additional_args = func_get_args();
  995. array_shift($additional_args);
  996. array_shift($additional_args);
  997. $args = array_merge($args, $additional_args);
  998. $hook = $type . '_alter';
  999. foreach (module_list() as $module) {
  1000. if (module_hook($module, $hook)) {
  1001. call_user_func_array($module . '_' . $hook, $args);
  1002. }
  1003. }
  1004. }
  1005. /**
  1006. * @}
  1007. */
  1008. /**
  1009. * Copy of valid_url() that supports the webcal scheme.
  1010. *
  1011. * @see valid_url().
  1012. *
  1013. * @todo Replace with valid_url() when http://drupal.org/node/295021 is fixed.
  1014. */
  1015. function feeds_valid_url($url, $absolute = FALSE) {
  1016. if ($absolute) {
  1017. return (bool) preg_match("
  1018. /^ # Start at the beginning of the text
  1019. (?:ftp|https?|feed|webcal):\/\/ # Look for ftp, http, https, feed or webcal schemes
  1020. (?: # Userinfo (optional) which is typically
  1021. (?:(?:[\w\.\-\+!$&'\(\)*\+,;=]|%[0-9a-f]{2})+:)* # a username or a username and password
  1022. (?:[\w\.\-\+%!$&'\(\)*\+,;=]|%[0-9a-f]{2})+@ # combination
  1023. )?
  1024. (?:
  1025. (?:[a-z0-9\-\.]|%[0-9a-f]{2})+ # A domain name or a IPv4 address
  1026. |(?:\[(?:[0-9a-f]{0,4}:)*(?:[0-9a-f]{0,4})\]) # or a well formed IPv6 address
  1027. )
  1028. (?::[0-9]+)? # Server port number (optional)
  1029. (?:[\/|\?]
  1030. (?:[|\w#!:\.\?\+=&@$'~*,;\/\(\)\[\]\-]|%[0-9a-f]{2}) # The path and query (optional)
  1031. *)?
  1032. $/xi", $url);
  1033. }
  1034. else {
  1035. return (bool) preg_match("/^(?:[\w#!:\.\?\+=&@$'~*,;\/\(\)\[\]\-]|%[0-9a-f]{2})+$/i", $url);
  1036. }
  1037. }
  1038. /**
  1039. * Registers a feed subscription job for execution on feeds_exit().
  1040. *
  1041. * @param array $job
  1042. * Information about a new job to queue; or if set to NULL (default), leaves
  1043. * the current queued jobs unchanged.
  1044. *
  1045. * @return
  1046. * An array of subscribe jobs to process.
  1047. *
  1048. * @see feeds_exit()
  1049. * @see feeds_get_subscription_jobs()
  1050. */
  1051. function feeds_set_subscription_job(array $job = NULL) {
  1052. $jobs = &drupal_static(__FUNCTION__, array());
  1053. if (isset($job)) {
  1054. $jobs[] = $job;
  1055. }
  1056. return $jobs;
  1057. }
  1058. /**
  1059. * Returns the list of queued jobs to be run.
  1060. *
  1061. * @return
  1062. * An array of subscribe jobs to process.
  1063. *
  1064. * @see feeds_set_subscription_job()
  1065. */
  1066. function feeds_get_subscription_jobs() {
  1067. return feeds_set_subscription_job();
  1068. }
  1069. /**
  1070. * Implements hook_entity_property_info_alter().
  1071. */
  1072. function feeds_entity_property_info_alter(&$info) {
  1073. // Gather entities supported by Feeds processors.
  1074. $processors = FeedsPlugin::byType('processor');
  1075. $supported_entities = array();
  1076. foreach ($processors as $processor) {
  1077. $instance = feeds_plugin($processor['handler']['class'], '__none__');
  1078. if (method_exists($instance, 'entityType')) {
  1079. $supported_entities[] = $instance->entityType();
  1080. }
  1081. }
  1082. // Feeds processors can fake the entity info. Only set the property for
  1083. // defined entities.
  1084. $supported_entities = array_intersect(array_keys($info), $supported_entities);
  1085. foreach ($supported_entities as $entity_type) {
  1086. $info[$entity_type]['properties']['feed_nid'] = array(
  1087. 'label' => 'Feed NID',
  1088. 'type' => 'integer',
  1089. 'description' => t('Nid of the Feed Node that imported this entity.'),
  1090. 'getter callback' => 'feeds_get_feed_nid_entity_callback',
  1091. );
  1092. }
  1093. }
  1094. /**
  1095. * Gets the feed_nid for an entity for use in entity metadata.
  1096. */
  1097. function feeds_get_feed_nid_entity_callback($entity, array $options, $name, $entity_type) {
  1098. list($entity_id, , ) = entity_extract_ids($entity_type, $entity);
  1099. $feed_nid = feeds_get_feed_nid($entity_id, $entity_type);
  1100. if ($feed_nid === FALSE) {
  1101. return NULL;
  1102. }
  1103. return $feed_nid;
  1104. }
  1105. /**
  1106. * Implements hook_file_download().
  1107. */
  1108. function feeds_file_download($uri) {
  1109. $id = db_query("SELECT id FROM {feeds_source} WHERE source = :uri", array(':uri' => $uri))->fetchField();
  1110. if (!$id) {
  1111. // File is not associated with a feed.
  1112. return;
  1113. }
  1114. // Get the file record based on the URI. If not in the database just return.
  1115. $files = file_load_multiple(array(), array('uri' => $uri));
  1116. foreach ($files as $item) {
  1117. // Since some database servers sometimes use a case-insensitive comparison
  1118. // by default, double check that the filename is an exact match.
  1119. if ($item->uri === $uri) {
  1120. $file = $item;
  1121. break;
  1122. }
  1123. }
  1124. if (!isset($file)) {
  1125. return;
  1126. }
  1127. // Check if this file belongs to Feeds.
  1128. $usage_list = file_usage_list($file);
  1129. if (!isset($usage_list['feeds'])) {
  1130. return;
  1131. }
  1132. if (!feeds_access('import', $id)) {
  1133. // User does not have permission to import this feed.
  1134. return -1;
  1135. }
  1136. // Return file headers.
  1137. return file_get_content_headers($file);
  1138. }
  1139. /**
  1140. * Feeds API version.
  1141. */
  1142. function feeds_api_version() {
  1143. $version = feeds_ctools_plugin_api('feeds', 'plugins');
  1144. return $version['version'];
  1145. }