feeds.module

  1. cis7 sites/all/modules/ulmus/feeds/feeds.module
  2. cle7 sites/all/modules/ulmus/feeds/feeds.module
  3. ecd7 sites/all/modules/ulmus/feeds/feeds.module
  4. elmsmedia7 sites/all/modules/ulmus/feeds/feeds.module
  5. harmony7 sites/all/modules/ulmus/feeds/feeds.module
  6. icor7 sites/all/modules/ulmus/feeds/feeds.module
  7. meedjum_blog7 sites/all/modules/ulmus/feeds/feeds.module
  8. mooc7 sites/all/modules/ulmus/feeds/feeds.module

Feeds - basic API functions and hook implementations.

Functions

Namesort descending Description
feeds_access Menu access callback.
feeds_alter Deprecated @todo This needs to be removed and drupal_alter() used. This is crazy dumb.
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_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_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

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