feeds.module 39 KB

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