FeedsProcessor.inc

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

Contains FeedsProcessor and related classes.

Constants

Classes

Namesort descending Description
FeedsAccessException Thrown if a an access check fails.
FeedsProcessor Abstract class, defines interface for processors.
FeedsValidationException Thrown if a validation fails.

File

sites/all/modules/ulmus/feeds/plugins/FeedsProcessor.inc
View source
  1. <?php
  2. /**
  3. * @file
  4. * Contains FeedsProcessor and related classes.
  5. */
  6. // Update mode for existing items.
  7. define('FEEDS_SKIP_EXISTING', 0);
  8. define('FEEDS_REPLACE_EXISTING', 1);
  9. define('FEEDS_UPDATE_EXISTING', 2);
  10. // Default limit for creating items on a page load, not respected by all
  11. // processors.
  12. define('FEEDS_PROCESS_LIMIT', 50);
  13. /**
  14. * Thrown if a validation fails.
  15. */
  16. class FeedsValidationException extends Exception {}
  17. /**
  18. * Thrown if a an access check fails.
  19. */
  20. class FeedsAccessException extends Exception {}
  21. /**
  22. * Abstract class, defines interface for processors.
  23. */
  24. abstract class FeedsProcessor extends FeedsPlugin {
  25. /**
  26. * @defgroup entity_api_wrapper Entity API wrapper.
  27. */
  28. /**
  29. * Entity type this processor operates on.
  30. */
  31. public abstract function entityType();
  32. /**
  33. * Create a new entity.
  34. *
  35. * @param $source
  36. * The feeds source that spawns this entity.
  37. *
  38. * @return
  39. * A new entity object.
  40. */
  41. protected abstract function newEntity(FeedsSource $source);
  42. /**
  43. * Load an existing entity.
  44. *
  45. * @param $source
  46. * The feeds source that spawns this entity.
  47. * @param $entity_id
  48. * The unique id of the entity that should be loaded.
  49. *
  50. * @return
  51. * A new entity object.
  52. */
  53. protected abstract function entityLoad(FeedsSource $source, $entity_id);
  54. /**
  55. * Validate an entity.
  56. *
  57. * @throws FeedsValidationException $e
  58. * If validation fails.
  59. */
  60. protected function entityValidate($entity) {}
  61. /**
  62. * Access check for saving an enity.
  63. *
  64. * @param $entity
  65. * Entity to be saved.
  66. *
  67. * @throws FeedsAccessException $e
  68. * If the access check fails.
  69. */
  70. protected function entitySaveAccess($entity) {}
  71. /**
  72. * Save an entity.
  73. *
  74. * @param $entity
  75. * Entity to be saved.
  76. */
  77. protected abstract function entitySave($entity);
  78. /**
  79. * Delete a series of entities.
  80. *
  81. * @param $entity_ids
  82. * Array of unique identity ids to be deleted.
  83. */
  84. protected abstract function entityDeleteMultiple($entity_ids);
  85. /**
  86. * Wrap entity_get_info() into a method so that extending classes can override
  87. * it and more entity information. Allowed additional keys:
  88. *
  89. * 'label plural' ... the plural label of an entity type.
  90. */
  91. protected function entityInfo() {
  92. return entity_get_info($this->entityType());
  93. }
  94. /**
  95. * @}
  96. */
  97. /**
  98. * Process the result of the parsing stage.
  99. *
  100. * @param FeedsSource $source
  101. * Source information about this import.
  102. * @param FeedsParserResult $parser_result
  103. * The result of the parsing stage.
  104. */
  105. public function process(FeedsSource $source, FeedsParserResult $parser_result) {
  106. $state = $source->state(FEEDS_PROCESS);
  107. while ($item = $parser_result->shiftItem()) {
  108. // Check if this item already exists.
  109. $entity_id = $this->existingEntityId($source, $parser_result);
  110. $skip_existing = $this->config['update_existing'] == FEEDS_SKIP_EXISTING;
  111. // If it exists, and we are not updating, pass onto the next item.
  112. if ($entity_id && $skip_existing) {
  113. continue;
  114. }
  115. $hash = $this->hash($item);
  116. $changed = ($hash !== $this->getHash($entity_id));
  117. $force_update = $this->config['skip_hash_check'];
  118. // Do not proceed if the item exists, has not changed, and we're not
  119. // forcing the update.
  120. if ($entity_id && !$changed && !$force_update) {
  121. continue;
  122. }
  123. try {
  124. // Build a new entity.
  125. if (empty($entity_id)) {
  126. $entity = $this->newEntity($source);
  127. $this->newItemInfo($entity, $source->feed_nid, $hash);
  128. }
  129. // Load an existing entity.
  130. else {
  131. $entity = $this->entityLoad($source, $entity_id);
  132. // The feeds_item table is always updated with the info for the most recently processed entity.
  133. // The only carryover is the entity_id.
  134. $this->newItemInfo($entity, $source->feed_nid, $hash);
  135. $entity->feeds_item->entity_id = $entity_id;
  136. }
  137. // Set property and field values.
  138. $this->map($source, $parser_result, $entity);
  139. $this->entityValidate($entity);
  140. // Allow modules to alter the entity before saving.
  141. module_invoke_all('feeds_presave', $source, $entity, $item);
  142. if (module_exists('rules')) {
  143. rules_invoke_event('feeds_import_'. $source->importer()->id, $entity);
  144. }
  145. // Enable modules to skip saving at all.
  146. if (!empty($entity->feeds_item->skip)) {
  147. continue;
  148. }
  149. // This will throw an exception on failure.
  150. $this->entitySaveAccess($entity);
  151. $this->entitySave($entity);
  152. // Track progress.
  153. if (empty($entity_id)) {
  154. $state->created++;
  155. }
  156. else {
  157. $state->updated++;
  158. }
  159. }
  160. // Something bad happened, log it.
  161. catch (Exception $e) {
  162. $state->failed++;
  163. drupal_set_message($e->getMessage(), 'warning');
  164. $message = $e->getMessage();
  165. $message .= '<h3>Original item</h3>';
  166. $message .= '<pre>' . var_export($item, TRUE) . '</pre>';
  167. $message .= '<h3>Entity</h3>';
  168. $message .= '<pre>' . var_export($entity, TRUE) . '</pre>';
  169. $source->log('import', $message, array(), WATCHDOG_ERROR);
  170. }
  171. }
  172. // Set messages if we're done.
  173. if ($source->progressImporting() != FEEDS_BATCH_COMPLETE) {
  174. return;
  175. }
  176. $info = $this->entityInfo();
  177. $tokens = array(
  178. '@entity' => strtolower($info['label']),
  179. '@entities' => strtolower($info['label plural']),
  180. );
  181. $messages = array();
  182. if ($state->created) {
  183. $messages[] = array(
  184. 'message' => format_plural(
  185. $state->created,
  186. 'Created @number @entity.',
  187. 'Created @number @entities.',
  188. array('@number' => $state->created) + $tokens
  189. ),
  190. );
  191. }
  192. if ($state->updated) {
  193. $messages[] = array(
  194. 'message' => format_plural(
  195. $state->updated,
  196. 'Updated @number @entity.',
  197. 'Updated @number @entities.',
  198. array('@number' => $state->updated) + $tokens
  199. ),
  200. );
  201. }
  202. if ($state->failed) {
  203. $messages[] = array(
  204. 'message' => format_plural(
  205. $state->failed,
  206. 'Failed importing @number @entity.',
  207. 'Failed importing @number @entities.',
  208. array('@number' => $state->failed) + $tokens
  209. ),
  210. 'level' => WATCHDOG_ERROR,
  211. );
  212. }
  213. if (empty($messages)) {
  214. $messages[] = array(
  215. 'message' => t('There are no new @entities.', array('@entities' => strtolower($info['label plural']))),
  216. );
  217. }
  218. foreach ($messages as $message) {
  219. drupal_set_message($message['message']);
  220. $source->log('import', $message['message'], array(), isset($message['level']) ? $message['level'] : WATCHDOG_INFO);
  221. }
  222. }
  223. /**
  224. * Remove all stored results or stored results up to a certain time for a
  225. * source.
  226. *
  227. * @param FeedsSource $source
  228. * Source information for this expiry. Implementers should only delete items
  229. * pertaining to this source. The preferred way of determining whether an
  230. * item pertains to a certain souce is by using $source->feed_nid. It is the
  231. * processor's responsibility to store the feed_nid of an imported item in
  232. * the processing stage.
  233. */
  234. public function clear(FeedsSource $source) {
  235. $state = $source->state(FEEDS_PROCESS_CLEAR);
  236. // Build base select statement.
  237. $info = $this->entityInfo();
  238. $select = db_select($info['base table'], 'e');
  239. $select->addField('e', $info['entity keys']['id'], 'entity_id');
  240. $select->join(
  241. 'feeds_item',
  242. 'fi',
  243. "e.{$info['entity keys']['id']} = fi.entity_id AND fi.entity_type = '{$this->entityType()}'");
  244. $select->condition('fi.id', $this->id);
  245. $select->condition('fi.feed_nid', $source->feed_nid);
  246. // If there is no total, query it.
  247. if (!$state->total) {
  248. $state->total = $select->countQuery()
  249. ->execute()
  250. ->fetchField();
  251. }
  252. // Delete a batch of entities.
  253. $entities = $select->range(0, $this->getLimit())->execute();
  254. $entity_ids = array();
  255. foreach ($entities as $entity) {
  256. $entity_ids[$entity->entity_id] = $entity->entity_id;
  257. }
  258. $this->entityDeleteMultiple($entity_ids);
  259. // Report progress, take into account that we may not have deleted as
  260. // many items as we have counted at first.
  261. if (count($entity_ids)) {
  262. $state->deleted += count($entity_ids);
  263. $state->progress($state->total, $state->deleted);
  264. }
  265. else {
  266. $state->progress($state->total, $state->total);
  267. }
  268. // Report results when done.
  269. if ($source->progressClearing() == FEEDS_BATCH_COMPLETE) {
  270. if ($state->deleted) {
  271. $message = format_plural(
  272. $state->deleted,
  273. 'Deleted @number @entity',
  274. 'Deleted @number @entities',
  275. array(
  276. '@number' => $state->deleted,
  277. '@entity' => strtolower($info['label']),
  278. '@entities' => strtolower($info['label plural']),
  279. )
  280. );
  281. $source->log('clear', $message, array(), WATCHDOG_INFO);
  282. drupal_set_message($message);
  283. }
  284. else {
  285. drupal_set_message(t('There are no @entities to be deleted.', array('@entities' => $info['label plural'])));
  286. }
  287. }
  288. }
  289. /*
  290. * Report number of items that can be processed per call.
  291. *
  292. * 0 means 'unlimited'.
  293. *
  294. * If a number other than 0 is given, Feeds parsers that support batching
  295. * will only deliver this limit to the processor.
  296. *
  297. * @see FeedsSource::getLimit()
  298. * @see FeedsCSVParser::parse()
  299. */
  300. public function getLimit() {
  301. return variable_get('feeds_process_limit', FEEDS_PROCESS_LIMIT);
  302. }
  303. /**
  304. * Delete feed items younger than now - $time. Do not invoke expire on a
  305. * processor directly, but use FeedsImporter::expire() instead.
  306. *
  307. * @see FeedsImporter::expire().
  308. * @see FeedsDataProcessor::expire().
  309. *
  310. * @param $time
  311. * If implemented, all items produced by this configuration that are older
  312. * than REQUEST_TIME - $time should be deleted.
  313. * If $time === NULL processor should use internal configuration.
  314. *
  315. * @return
  316. * FEEDS_BATCH_COMPLETE if all items have been processed, a float between 0
  317. * and 0.99* indicating progress otherwise.
  318. */
  319. public function expire($time = NULL) {
  320. return FEEDS_BATCH_COMPLETE;
  321. }
  322. /**
  323. * Counts the number of items imported by this processor.
  324. */
  325. public function itemCount(FeedsSource $source) {
  326. return db_query("SELECT count(*) FROM {feeds_item} WHERE id = :id AND entity_type = :entity_type AND feed_nid = :feed_nid", array(':id' => $this->id, ':entity_type' => $this->entityType(), ':feed_nid' => $source->feed_nid))->fetchField();
  327. }
  328. /**
  329. * Execute mapping on an item.
  330. *
  331. * This method encapsulates the central mapping functionality. When an item is
  332. * processed, it is passed through map() where the properties of $source_item
  333. * are mapped onto $target_item following the processor's mapping
  334. * configuration.
  335. *
  336. * For each mapping FeedsParser::getSourceElement() is executed to retrieve
  337. * the source element, then FeedsProcessor::setTargetElement() is invoked
  338. * to populate the target item properly. Alternatively a
  339. * hook_x_targets_alter() may have specified a callback for a mapping target
  340. * in which case the callback is asked to populate the target item instead of
  341. * FeedsProcessor::setTargetElement().
  342. *
  343. * @ingroup mappingapi
  344. *
  345. * @see hook_feeds_parser_sources_alter()
  346. * @see hook_feeds_data_processor_targets_alter()
  347. * @see hook_feeds_node_processor_targets_alter()
  348. * @see hook_feeds_term_processor_targets_alter()
  349. * @see hook_feeds_user_processor_targets_alter()
  350. */
  351. protected function map(FeedsSource $source, FeedsParserResult $result, $target_item = NULL) {
  352. // Static cache $targets as getMappingTargets() may be an expensive method.
  353. static $sources;
  354. if (!isset($sources[$this->id])) {
  355. $sources[$this->id] = feeds_importer($this->id)->parser->getMappingSources();
  356. }
  357. static $targets;
  358. if (!isset($targets[$this->id])) {
  359. $targets[$this->id] = $this->getMappingTargets();
  360. }
  361. $parser = feeds_importer($this->id)->parser;
  362. if (empty($target_item)) {
  363. $target_item = array();
  364. }
  365. // Many mappers add to existing fields rather than replacing them. Hence we
  366. // need to clear target elements of each item before mapping in case we are
  367. // mapping on a prepopulated item such as an existing node.
  368. foreach ($this->config['mappings'] as $mapping) {
  369. if (isset($targets[$this->id][$mapping['target']]['real_target'])) {
  370. unset($target_item->{$targets[$this->id][$mapping['target']]['real_target']});
  371. }
  372. elseif (isset($target_item->{$mapping['target']})) {
  373. unset($target_item->{$mapping['target']});
  374. }
  375. }
  376. /*
  377. This is where the actual mapping happens: For every mapping we envoke
  378. the parser's getSourceElement() method to retrieve the value of the source
  379. element and pass it to the processor's setTargetElement() to stick it
  380. on the right place of the target item.
  381. If the mapping specifies a callback method, use the callback instead of
  382. setTargetElement().
  383. */
  384. self::loadMappers();
  385. foreach ($this->config['mappings'] as $mapping) {
  386. // Retrieve source element's value from parser.
  387. if (isset($sources[$this->id][$mapping['source']]) &&
  388. is_array($sources[$this->id][$mapping['source']]) &&
  389. isset($sources[$this->id][$mapping['source']]['callback']) &&
  390. function_exists($sources[$this->id][$mapping['source']]['callback'])) {
  391. $callback = $sources[$this->id][$mapping['source']]['callback'];
  392. $value = $callback($source, $result, $mapping['source']);
  393. }
  394. else {
  395. $value = $parser->getSourceElement($source, $result, $mapping['source']);
  396. }
  397. // Map the source element's value to the target.
  398. if (isset($targets[$this->id][$mapping['target']]) &&
  399. is_array($targets[$this->id][$mapping['target']]) &&
  400. isset($targets[$this->id][$mapping['target']]['callback']) &&
  401. function_exists($targets[$this->id][$mapping['target']]['callback'])) {
  402. $callback = $targets[$this->id][$mapping['target']]['callback'];
  403. $callback($source, $target_item, $mapping['target'], $value, $mapping);
  404. }
  405. else {
  406. $this->setTargetElement($source, $target_item, $mapping['target'], $value, $mapping);
  407. }
  408. }
  409. return $target_item;
  410. }
  411. /**
  412. * Per default, don't support expiry. If processor supports expiry of imported
  413. * items, return the time after which items should be removed.
  414. */
  415. public function expiryTime() {
  416. return FEEDS_EXPIRE_NEVER;
  417. }
  418. /**
  419. * Declare default configuration.
  420. */
  421. public function configDefaults() {
  422. return array(
  423. 'mappings' => array(),
  424. 'update_existing' => FEEDS_SKIP_EXISTING,
  425. 'input_format' => NULL,
  426. 'skip_hash_check' => FALSE,
  427. );
  428. }
  429. /**
  430. * Overrides parent::configForm().
  431. */
  432. public function configForm(&$form_state) {
  433. $info = $this->entityInfo();
  434. $form = array();
  435. $tokens = array('@entities' => strtolower($info['label plural']));
  436. $form['update_existing'] = array(
  437. '#type' => 'radios',
  438. '#title' => t('Update existing @entities', $tokens),
  439. '#description' =>
  440. t('Existing @entities will be determined using mappings that are a "unique target".', $tokens),
  441. '#options' => array(
  442. FEEDS_SKIP_EXISTING => t('Do not update existing @entities', $tokens),
  443. FEEDS_UPDATE_EXISTING => t('Update existing @entities', $tokens),
  444. ),
  445. '#default_value' => $this->config['update_existing'],
  446. );
  447. global $user;
  448. $formats = filter_formats($user);
  449. foreach ($formats as $format) {
  450. $format_options[$format->format] = $format->name;
  451. }
  452. $form['skip_hash_check'] = array(
  453. '#type' => 'checkbox',
  454. '#title' => t('Skip hash check'),
  455. '#description' => t('Force update of items even if item source data did not change.'),
  456. '#default_value' => $this->config['skip_hash_check'],
  457. );
  458. $form['input_format'] = array(
  459. '#type' => 'select',
  460. '#title' => t('Text format'),
  461. '#description' => t('Select the input format for the body field of the nodes to be created.'),
  462. '#options' => $format_options,
  463. '#default_value' => isset($this->config['input_format']) ? $this->config['input_format'] : 'plain_text',
  464. '#required' => TRUE,
  465. );
  466. return $form;
  467. }
  468. /**
  469. * Get mappings.
  470. */
  471. public function getMappings() {
  472. return isset($this->config['mappings']) ? $this->config['mappings'] : array();
  473. }
  474. /**
  475. * Declare possible mapping targets that this processor exposes.
  476. *
  477. * @ingroup mappingapi
  478. *
  479. * @return
  480. * An array of mapping targets. Keys are paths to targets
  481. * separated by ->, values are TRUE if target can be unique,
  482. * FALSE otherwise.
  483. */
  484. public function getMappingTargets() {
  485. return array(
  486. 'url' => array(
  487. 'name' => t('URL'),
  488. 'description' => t('The external URL of the item. E. g. the feed item URL in the case of a syndication feed. May be unique.'),
  489. 'optional_unique' => TRUE,
  490. ),
  491. 'guid' => array(
  492. 'name' => t('GUID'),
  493. 'description' => t('The globally unique identifier of the item. E. g. the feed item GUID in the case of a syndication feed. May be unique.'),
  494. 'optional_unique' => TRUE,
  495. ),
  496. );
  497. }
  498. /**
  499. * Set a concrete target element. Invoked from FeedsProcessor::map().
  500. *
  501. * @ingroup mappingapi
  502. */
  503. public function setTargetElement(FeedsSource $source, $target_item, $target_element, $value) {
  504. switch ($target_element) {
  505. case 'url':
  506. case 'guid':
  507. $target_item->feeds_item->$target_element = $value;
  508. break;
  509. default:
  510. $target_item->$target_element = $value;
  511. break;
  512. }
  513. }
  514. /**
  515. * Retrieve the target entity's existing id if available. Otherwise return 0.
  516. *
  517. * @ingroup mappingapi
  518. *
  519. * @param FeedsSource $source
  520. * The source information about this import.
  521. * @param $result
  522. * A FeedsParserResult object.
  523. *
  524. * @return
  525. * The serial id of an entity if found, 0 otherwise.
  526. */
  527. protected function existingEntityId(FeedsSource $source, FeedsParserResult $result) {
  528. $query = db_select('feeds_item')
  529. ->fields('feeds_item', array('entity_id'))
  530. ->condition('feed_nid', $source->feed_nid)
  531. ->condition('entity_type', $this->entityType())
  532. ->condition('id', $source->id);
  533. // Iterate through all unique targets and test whether they do already
  534. // exist in the database.
  535. foreach ($this->uniqueTargets($source, $result) as $target => $value) {
  536. switch ($target) {
  537. case 'url':
  538. $entity_id = $query->condition('url', $value)->execute()->fetchField();
  539. break;
  540. case 'guid':
  541. $entity_id = $query->condition('guid', $value)->execute()->fetchField();
  542. break;
  543. }
  544. if (isset($entity_id)) {
  545. // Return with the content id found.
  546. return $entity_id;
  547. }
  548. }
  549. return 0;
  550. }
  551. /**
  552. * Utility function that iterates over a target array and retrieves all
  553. * sources that are unique.
  554. *
  555. * @param $batch
  556. * A FeedsImportBatch.
  557. *
  558. * @return
  559. * An array where the keys are target field names and the values are the
  560. * elements from the source item mapped to these targets.
  561. */
  562. protected function uniqueTargets(FeedsSource $source, FeedsParserResult $result) {
  563. $parser = feeds_importer($this->id)->parser;
  564. $targets = array();
  565. foreach ($this->config['mappings'] as $mapping) {
  566. if ($mapping['unique']) {
  567. // Invoke the parser's getSourceElement to retrieve the value for this
  568. // mapping's source.
  569. $targets[$mapping['target']] = $parser->getSourceElement($source, $result, $mapping['source']);
  570. }
  571. }
  572. return $targets;
  573. }
  574. /**
  575. * Adds Feeds specific information on $entity->feeds_item.
  576. *
  577. * @param $entity
  578. * The entity object to be populated with new item info.
  579. * @param $feed_nid
  580. * The feed nid of the source that produces this entity.
  581. * @param $hash
  582. * The fingerprint of the source item.
  583. */
  584. protected function newItemInfo($entity, $feed_nid, $hash = '') {
  585. $entity->feeds_item = new stdClass();
  586. $entity->feeds_item->entity_id = 0;
  587. $entity->feeds_item->entity_type = $this->entityType();
  588. $entity->feeds_item->id = $this->id;
  589. $entity->feeds_item->feed_nid = $feed_nid;
  590. $entity->feeds_item->imported = REQUEST_TIME;
  591. $entity->feeds_item->hash = $hash;
  592. $entity->feeds_item->url = '';
  593. $entity->feeds_item->guid = '';
  594. }
  595. /**
  596. * Loads existing entity information and places it on $entity->feeds_item.
  597. *
  598. * @param $entity
  599. * The entity object to load item info for. Id key must be present.
  600. *
  601. * @return
  602. * TRUE if item info could be loaded, false if not.
  603. */
  604. protected function loadItemInfo($entity) {
  605. $entity_info = entity_get_info($this->entityType());
  606. $key = $entity_info['entity keys']['id'];
  607. if ($item_info = feeds_item_info_load($this->entityType(), $entity->$key)) {
  608. $entity->feeds_item = $item_info;
  609. return TRUE;
  610. }
  611. return FALSE;
  612. }
  613. /**
  614. * Create MD5 hash of item and mappings array.
  615. *
  616. * Include mappings as a change in mappings may have an affect on the item
  617. * produced.
  618. *
  619. * @return Always returns a hash, even with empty, NULL, FALSE:
  620. * Empty arrays return 40cd750bba9870f18aada2478b24840a
  621. * Empty/NULL/FALSE strings return d41d8cd98f00b204e9800998ecf8427e
  622. */
  623. protected function hash($item) {
  624. static $serialized_mappings;
  625. if (!$serialized_mappings) {
  626. $serialized_mappings = serialize($this->config['mappings']);
  627. }
  628. return hash('md5', serialize($item) . $serialized_mappings);
  629. }
  630. /**
  631. * Retrieves the MD5 hash of $entity_id from the database.
  632. *
  633. * @return string
  634. * Empty string if no item is found, hash otherwise.
  635. */
  636. protected function getHash($entity_id) {
  637. if ($hash = db_query("SELECT hash FROM {feeds_item} WHERE entity_type = :type AND entity_id = :id", array(':type' => $this->entityType(), ':id' => $entity_id))->fetchField()) {
  638. // Return with the hash.
  639. return $hash;
  640. }
  641. return '';
  642. }
  643. }
Error | ELMSLN API

Error

×

Error message

  • Warning: Cannot modify header information - headers already sent by (output started at /var/www/html/elmsln_community/api.elmsln.org/includes/common.inc:2791) in drupal_send_headers() (line 1499 of /var/www/html/elmsln_community/api.elmsln.org/includes/bootstrap.inc).
  • Error: Call to undefined function apc_delete() in DrupalAPCCache->clear() (line 289 of /var/www/html/elmsln_community/api.elmsln.org/sites/all/modules/apc/drupal_apc_cache.inc).
The website encountered an unexpected error. Please try again later.